Зачем sed
Когда надо переписать строки в потоке (логе, конфиге, выводе команды),
sed - самый быстрый инструмент: одна команда, без временных файлов,
без Python'а. Работает построчно (line-oriented), без буферизации
всего входа в память. Идеально для пайпов и find -exec.
Если задача шире построчной замены - бери [[cmd-awk|awk]] или Python.
Базовый синтаксис
sed [OPTIONS] 'SCRIPT' [FILE...]
sed [OPTIONS] -f script.sed [FILE...]
Без файла читает stdin. Скрипт - набор команд через ; или -e.
По дефолту печатает каждую обработанную строку (если не отключить -n).
Команда s - замена
Самая частая команда:
sed 's/old/new/' file.txt # первое совпадение в строке
sed 's/old/new/g' file.txt # все совпадения (global)
sed 's/old/new/2' file.txt # только 2-е совпадение
sed 's/old/new/gi' file.txt # global + case-insensitive
Разделитель не обязан быть / - удобно для путей:
sed 's|/old/path|/new/path|g' file
sed 's#http://#https://#g' urls.txt
-E для ERE
По дефолту sed использует BRE, как у grep без -E. То есть +, ?,
(, ), {, } нужно экранировать. Включи -E (или -r в GNU):
sed -E 's/(error|warn): (.*)/[\1] \2/' app.log
Группы \1, \2 доступны и в BRE, но скобки в BRE надо \(\).
In-place редактирование -i
sed -i 's/foo/bar/g' file.txt # GNU sed
sed -i.bak 's/foo/bar/g' file.txt # с backup file.txt.bak
sed -i '' 's/foo/bar/g' file.txt # macOS BSD sed - пустое расширение обязательно
Кроссплатформенный кошмар: GNU sed -i принимает опциональный аргумент
слитно (sed -i.bak), BSD - требует пробел и обязательное расширение
(sed -i '' ...). На macOS чаще ставят gsed через brew.
Адресация - где применять команду
sed '5d' file.txt # удалить 5-ю строку
sed '5,10d' file # удалить строки 5-10
sed '/^#/d' file # удалить комментарии
sed '/start/,/end/d' file # удалить блок от start до end включительно
sed '$d' file # удалить последнюю
sed '1,/^$/d' file # удалить с начала до первой пустой
sed -n '/ERROR/p' file # напечатать только ERROR-строки (как grep)
Адресные диапазоны - убийственно полезная фича: вырезаешь куски конфигов, фильтруешь блоки логов между маркерами.
Команды кроме s
| Команда | Что делает |
|---|---|
d | удалить (не печатать) текущую строку |
p | напечатать (имеет смысл с -n) |
q | quit - сразу выйти |
= | напечатать номер строки |
i\TEXT | вставить TEXT перед строкой |
a\TEXT | добавить TEXT после строки |
c\TEXT | заменить целиком на TEXT |
y/abc/ABC/ | посимвольная транслитерация (как tr) |
r FILE | вставить содержимое файла после совпавшей строки |
w FILE | записать совпавшие строки в FILE |
sed '/^server {/i\# auto-managed' nginx.conf # коммент перед каждым server-блокомsed -n '5,10p' file # быстрее `head -10 | tail -6`
sed '0,/foo/{s/foo/bar/}' file # заменить ТОЛЬКО первое foo во всём файлеMulti-line: pattern space + hold space
sed работает с двумя буферами:
- pattern space - текущая строка (по умолчанию)
- hold space - вторичная память, между обработкой строк
Команды между ними:
h- скопировать pattern → hold (затирает)H- добавить pattern в hold (через \n)g- скопировать hold → pattern (затирает)G- добавить hold в patternx- swap
Классический трюк - реверс строк (tac):
sed '1!G;h;$!d' file
Это уже территория где [[cmd-awk|awk]] или Python читаются легче.
Когда что-то пошло не так
sed: -i may not be used with stdin- после-iнужен файл, не пайпsed: 1: "...": invalid command codeна macOS - BSD-sed; ставьbrew install gnu-sedи зовиgsedunterminated 's' command- забыл закрывающий/или экранирование спецсимвола в шаблоне- Замена не сработала, хотя
grepнаходит -sedбез-Eждёт BRE; группа(foo|bar)без-E- литерал - In-place портит symlink - GNU sed по дефолту переименовывает,
разрывая ссылку. Используй
--follow-symlinks - Кодировка полетела - sed работает побайтово; для UTF-8 с
мульти-байтными классами
[[:alpha:]]нужен правильный locale (LC_ALL=ru_RU.UTF-8)
Альтернативы
awk- когда работаешь с полями, нужен счётчик, агрегацияperl -pe/perl -i- PCRE, lookahead, более вменяемая кросс-платформенностьsd(Rust) - современный CLI с PCRE-дефолтом и понятным синтаксисом