Обзор из множества полезных существующих ответов , дополненный пояснениями :
В приведенных здесь примерах используется упрощенный вариант использования: замените слово «foo» на «bar» только в первой соответствующей строке.
Из-за использования строк в кавычках ANSI C ($'...'
) для предоставления примерных строк ввода в качестве оболочки предполагается bash
, ksh
или zsh
.
GNU sed
только:
Ответ Бен Хоффштейна показывает нам, что GNU предоставляет расширение спецификации POSIX для sed
, которая допускает следующую двухадресную форму: 0,/re/
(re
представляет здесь произвольное регулярное выражение).
0,/re/
позволяет регулярному выражению соответствовать в самой первой строке также . Другими словами: такой адрес создаст диапазон от 1-й строки до и включая строку, которая соответствует re
- независимо от того, встречается ли re
в 1-й строке или в любой последующей строке.
- Сравните это с POSIX-совместимой формой
1,/re/
, которая создает диапазон, который совпадает с 1-й строки до и включая строку, которая соответствует re
на , следующих линии; другими словами: этот не будет обнаруживать первое вхождение совпадения re
, если оно произойдет в 1-й строке , а также предотвращает использование сокращения //
для повторного использования последнего использованного регулярного выражения (см. Следующий пункт). [1]
Если вы объедините адрес 0,/re/
с вызовом s/.../.../
(подстановка), который использует регулярное выражение с тем же , ваша команда будет эффективно выполнять подстановку только для first строка, которая соответствует re
.
sed
предоставляет удобный ярлык для повторного использования самого последнего примененного регулярного выражения : пустая пара разделителей, //
.
$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo
A только для POSIX-функций sed
, например, BSD (macOS) sed
(также будет работать с GNU sed
):
Поскольку 0,/re/
нельзя использовать и форма 1,/re/
не обнаружит re
, если это произойдет в самой первой строке (см. Выше), требуется специальная обработка для 1-й строки .
В ответе MikhailVS упоминается техника, приведенная в конкретном примере здесь:
$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo
Примечание:
Пустой ярлык регулярного выражения //
используется здесь дважды: один раз для конечной точки диапазона и один раз в вызове s
; в обоих случаях регулярное выражение foo
используется неявно, что позволяет нам не дублировать его, что делает как более короткий, так и более понятный код.
POSIX sed
нужны фактические символы новой строки после определенных функций, например после имени метки или даже ее пропуска, как в случае с t
здесь; Стратегическое разделение сценария на несколько вариантов -e
является альтернативой использованию фактических символов новой строки: заканчивайте каждый фрагмент сценария -e
там, где обычно требуется переход на новую строку.
1 s/foo/bar/
заменяет foo
только на 1-й строке, если она там найдена.
Если это так, t
ветвится до конца скрипта (пропускает оставшиеся команды в строке). (Функция t
переходит к метке, только если последний вызов s
выполнил фактическую замену; при отсутствии метки, как в данном случае, конец сценария разветвляется).
Когда это произойдет, адрес диапазона 1,//
, который обычно находит первое вхождение , начиная со строки 2 , будет не совпадать, а диапазон будет не обрабатываться, потому что адрес вычисляется, когда текущая строка уже 2
.
И наоборот, если в 1-й строке нет совпадений, 1,//
будет введено и найдет истинное первое совпадение.
Чистый эффект такой же, как у GNU sed
0,/re/
: заменяется только первое вхождение, происходит ли оно в 1-й строке или в любом другом.
Подходы без дальности действия
ответ Потонга демонстрирует цикл техники , которые обходят необходимость в диапазоне ; поскольку он использует синтаксис GNU sed
, здесь приведены POSIX-совместимые эквиваленты :
Техника цикла 1: при первом совпадении выполните подстановку, затем введите цикл, который просто печатает оставшиеся строки как есть :
$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo
Loop техника 2, для только для маленьких файлов : прочитать весь ввод в память, а затем выполнить одну подстановку .
$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo
[1] 1.61803 предоставляет примеры того, что происходит с 1,/re/
, с последующим s//
и без него:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'
выход $'1bar\n2bar'
; то есть, обе строки были обновлены, потому что номер строки 1
соответствует 1-й строке, а регулярное выражение /foo/
- конец диапазона - затем ищется только для запуска на next линия. Следовательно, обе строки выбраны в этом случае, и замена s/foo/bar/
выполняется для обеих из них.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
терпит неудачу : с sed: first RE may not be empty
(BSD / macOS) и sed: -e expression #1, char 0: no previous regular expression
(GNU), потому что во время обработки 1-й строки (из-за номера строки 1
запускается range), регулярное выражение еще не применено, поэтому //
ни к чему не относится.
За исключением специального синтаксиса 0,/re/
GNU sed
, любой диапазон , начинающийся с номера строки , фактически исключает использование //
.