Learning Platform
Глоссарий Troubleshooting
Урок 09.01 · 25 мин
Средний
sedStream editorSubstitutionRegexIn-place edit

Что такое sed

sed (stream editor) — потоковый редактор: читает input построчно, применяет команды редактирования, выводит результат. Не открывает файл целиком (как vim), не требует pattern + action как awk — просто transformation pipeline для текста.

Создан в 1973 году Lee McMahon в Bell Labs как inline-обёртка над ed. Основная сила — substitution (s command) с regex.

$ echo "hello world" | sed 's/world/sed/'
hello sed

$ echo "user=alice id=42" | sed 's/alice/bob/'
user=bob id=42

Базовая команда s/PATTERN/REPLACEMENT/FLAGS — старейший и самый используемый паттерн в sed.

Базовый s-command

sed 's/OLD/NEW/' file       # заменить первое вхождение OLD на NEW в каждой строке
sed 's/OLD/NEW/g' file      # заменить все вхождения (g = global)
sed 's/OLD/NEW/2' file      # заменить только 2-е вхождение в каждой строке
sed 's/OLD/NEW/I' file      # case-insensitive
sed 's/OLD/NEW/gi' file     # global + case-insensitive

Без g sed заменяет только первое вхождение OLD в каждой строке. Это часто удивляет: для replace-all всегда нужен g.

$ echo "foo bar foo bar foo" | sed 's/foo/XXX/'
XXX bar foo bar foo         # только первое

$ echo "foo bar foo bar foo" | sed 's/foo/XXX/g'
XXX bar XXX bar XXX         # все

Флаги после третьего слэша

Флаги команды s

После /NEW/ может идти комбинация модификаторов.

gglobal — заменить все вхождения в строкеБез g — только первое. С g — все. Можно комбинировать: gI, g2 не имеет смысла.
N (число)заменить только N-е вхождениеsed 's/X/Y/3' — заменить только 3-е вхождение X в каждой строке. Полезно для column-specific edits.
Icase-insensitive (GNU)GNU-расширение, не POSIX. На macOS BSD sed используется i (lowercase). Лучше быть осторожным с capitalization флагов.
pprint modified lineПолезно с -n: только модифицированные строки выводятся. sed -n 's/X/Y/p' file — выдаст только строки где была замена.
w FILEwrite modified to FILEsed 's/X/Y/w changes.txt' — копия модифицированных строк в changes.txt.
eexecute replacement (опасно!)GNU-расширение: запускает replacement как shell-команду. Большая security дыра — никогда не применяй к ненадёжному input.

Альтернативные разделители

Стандартный разделитель s/.../.../ — это /. Но если pattern или replacement содержат /, экранирование становится adского:

# [X] Сложно читать:
$ sed 's/\/usr\/local\/bin/\/usr\/bin/'

# [x] Любой printable-символ может быть разделителем:
$ sed 's|/usr/local/bin|/usr/bin|'
$ sed 's#/usr/local/bin#/usr/bin#'
$ sed 's@/usr/local/bin@/usr/bin@'

Общая идиома — | или # для путей с /. Идея sed: после s следует любой символ, который дальше используется как разделитель — никаких других требований.

$ echo "PATH=/usr/local/bin" | sed 's|/usr/local|/opt|'
PATH=/opt/bin

Captured groups: \1, \2, …

В BRE (basic regex) sed используются escaped группы \(...\) и back-references \1, \2:

$ echo "alice 42" | sed 's/\([a-z]*\) \([0-9]*\)/\2 \1/'
42 alice

\(...\) захватывают groups, \1 подставляет первую group в replacement. С -E (extended regex) — обычные (...) и \1:

$ echo "alice 42" | sed -E 's/([a-z]+) ([0-9]+)/\2 \1/'
42 alice

& — весь match

$ echo "amount: 150" | sed 's/[0-9]\+/<\&>/'
amount: <150>

& в replacement = вся совпавшая часть. Удобно для wrapping: добавить prefix/suffix к каждому совпадению.

# Обернуть все email в скобки
$ echo "Contact [email protected] or [email protected]" \
    | sed -E 's/[a-z]+@[a-z.]+/<&>/g'
Contact <[email protected]m> or <[email protected]m>

In-place edit: sed -i (CRITICAL difference GNU vs BSD)

sed -i модифицирует файл на месте. Это огромное удобство, но синтаксис различается на Linux vs macOS:

# GNU sed (Linux):
$ sed -i 's/foo/bar/g' file

# BSD sed (macOS):
$ sed -i '' 's/foo/bar/g' file
#       ^^^^ ОБЯЗАТЕЛЬНЫЙ пустой аргумент (suffix for backup)

На BSD -i требует аргумент: что использовать как suffix для backup. '' (пустая строка) — «не делать backup». Если опустить — sed создаст fileE (где E — первый символ команды).

Кросс-платформенный pattern:

# Backup-safe (создаёт file.bak)
$ sed -i.bak 's/foo/bar/g' file
# Работает на обеих платформах!

Это формально правильный POSIX: -i.bak создаёт file.bak с оригиналом перед редактированием.

В скриптах для CI/Linux-only можно sed -i ... без аргумента, но в shared dotfiles и portable-скриптах используй -i.bak или detect platform:

# detect platform:
if [[ "$(uname)" == "Darwin" ]]; then
    SED_INPLACE=(sed -i '')
else
    SED_INPLACE=(sed -i)
fi
"${SED_INPLACE[@]}" 's/foo/bar/g' file

-i под капотом

sed -i не пишет в файл «на лету» — это было бы небезопасно при сбое:

  1. Создаёт новый временный файл в той же директории
  2. Читает input, пишет модифицированный output в temp
  3. После завершения делает atomic rename() temp на target

Это значит: (1) права/owner файла могут измениться (sed создаёт temp от своего UID); (2) hardlinks теряются — старый inode остаётся, новый файл с тем же именем — другой inode. Если кто-то держит open() FD к старому файлу — он не увидит изменений.

$ ls -li file
12345 -rw-r--r-- 1 levo levo file

$ sed -i 's/x/y/' file

$ ls -li file
67890 -rw-r--r-- 1 levo levo file
# inode сменился!

DE-сценарии sed substitution

1. Замена пути конфигурации

# Поменять URL базы данных в конфигах:
$ sed -i 's|postgresql://old-db|postgresql://new-db|g' /etc/airflow/*.cfg

2. Удалить trailing whitespace

$ sed -i 's/[[:space:]]*$//' file.py
# Удаляет все trailing whitespace в конце строк
# Полезно перед git commit
pre-commit хуки — автоматическая очистка перед git commit

3. Анонимизация данных

# Заменить все email на <REDACTED>
$ sed -E 's/[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+/<REDACTED>/g' users.csv

4. Apply patches к множеству файлов

# Поменять deprecated parameter во всех Python:
$ find . -name '*.py' -exec sed -i 's/use_legacy_sql=True/use_legacy_sql=False/g' {} +

5. Удаление BOM (Byte Order Mark) из UTF-8

# BOM = три байта \xef\xbb\xbf в начале файла, иногда добавляется Windows-редакторами
$ sed -i '1s/^\xef\xbb\xbf//' file.csv

1s/.../.../ — apply substitution только к первой строке.

sed vs perl -pe

Для сложных substitutions perl часто удобнее:

$ perl -pe 's/foo/bar/g' file       # эквивалент sed 's/foo/bar/g'
$ perl -i -pe 's/foo/bar/g' file    # in-place

Perl всегда использует PCRE (\d, \w, \s, lookahead/lookbehind), не зависит от GNU/BSD. На macOS perl -i -pe идентичен Linux — нет cursed-разницы -i '' vs -i.

Многие DE-команды переходят на perl -pe для cross-platform pipelines:

# Удалить лишние пробелы в начале/конце:
$ perl -pe 's/^\s+|\s+$//g' file

Sed с stdin

$ cat data.csv | sed 's/,/|/g'
# Изменить delimiter CSV на pipe

$ curl -s api.example.com/users | sed 's/"//g'
# Убрать все кавычки из JSON (грубо!)

ВНИМАНИЕ: Для JSON используй jq, не sed. Для XML/HTML — не используй sed («cthulhu fhtagn»). Sed хорош для построчных текстовых трансформаций.

Попробуй сам

  1. Простая замена:
    echo "hello world" | sed 's/hello/hi/'
  2. Global vs single:
    echo "aaa bbb aaa" | sed 's/aaa/X/'
    echo "aaa bbb aaa" | sed 's/aaa/X/g'
  3. Captured groups:
    echo "John Doe" | sed -E 's/(\w+) (\w+)/\2, \1/'
    # -> "Doe, John"
  4. In-place edit с backup:
    echo "foo bar foo" > /tmp/test.txt
    sed -i.bak 's/foo/REPLACED/g' /tmp/test.txt
    cat /tmp/test.txt
    cat /tmp/test.txt.bak    # original
  5. Альтернативный delimiter:
    echo "/usr/local/bin" | sed 's|/usr|/opt|'

macOS-различия

ОпцияGNU (Linux)BSD (macOS)Кросс-платформ
-i (in-place)sed -i 's/x/y/'sed -i '' 's/x/y/'sed -i.bak 's/x/y/'
-E (extended regex)поддержанподдержан-E
\b word boundaryработаетработает[[:space:]] или [[:<:]]/[[:>:]]
\d, \w, \sНЕТ (только PCRE/perl)НЕТ[0-9], [[:alnum:]_], [[:space:]]
-i NUM (line address)работаетработаетOK

Для action production-скриптов где нужна portable sed — лучше использовать perl -pe или awk.

Проверка знанийKnowledge check
Ты пишешь скрипт deploy.sh, который должен работать и на Mac mini сервере, и в Linux Docker. Скрипт должен заменить строку 'env=staging' на 'env=production' в файле config.yaml. Какая команда корректна для обеих платформ?
ОтветAnswer
sed -i.bak 's/env=staging/env=production/' config.yaml. Это POSIX-совместимый синтаксис: -i.bak означает 'in-place edit с backup в файл config.yaml.bak'. На GNU sed (Linux) -i.bak работает корректно; на BSD sed (macOS) -i.bak тоже работает. Альтернатива без backup file: либо detect platform через uname и использовать массив (SED_INPLACE+=(-i); или (-i '')), либо переключиться на perl -i -pe 's/env=staging/env=production/' config.yaml — perl на обеих платформах работает идентично, не имеет cursed-различия с -i. В чисто Linux-окружении (CI, Docker) можно просто sed -i. Если backup-файл не нужен — после скрипта можно rm config.yaml.bak. Главное: НЕ писать 'sed -i ...' и ожидать, что это сработает на macOS — там нужен -i '' с пустым аргументом или -i.bak.

Главное

  • sed 's/OLD/NEW/' — заменить первое вхождение в каждой строке. s/OLD/NEW/g — все.
  • Альтернативные разделители: s|...|...|, s#...#...# — для путей с /.
  • Captured groups: \(...\) в BRE, (...) в -E. Back-references \1, \2. & = весь match.
  • sed -i различается на GNU vs BSD. Portable: sed -i.bak ....
  • Под капотом -i создаёт temp-файл + atomic rename — inode меняется, FD-watchers могут не увидеть изменения.
  • Для cross-platform сложного sed — переключайся на perl -pe.
  • НЕ парси JSON/XML/HTML через sed.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Команда 'echo "aaa bbb aaa" | sed "s/aaa/X/"' выводит 'X bbb aaa'. Почему второе 'aaa' не заменилось?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5