Вставьте строку после первого совпадения, используя sed

245

По какой-то причине я не могу найти простой ответ на этот вопрос, и сейчас я немного переживаю. Как бы я вставил строку выбора после первой строки, соответствующей определенной строке, используяsed команду. У меня есть ...

CLIENTSCRIPT="foo"
CLIENTFILE="bar"

И я хочу вставить строку после CLIENTSCRIPT=строки, в результате чего ...

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"
user2150250
источник

Ответы:

379

Попробуйте сделать это с помощью GNU sed:

sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

если вы хотите заменить на месте , используйте

sed -i '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

Вывод

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"

доктор

  • см. sed doc и поиск \a(приложение)
Жиль Квено
источник
Спасибо! И чтобы заставить его записывать обратно в файл, не распечатывая содержимое файла?
user2150250
11
Обратите внимание, что оба предполагают GNU sed. Это не стандартный sedсинтаксис и не будет работать с любой другой sedреализацией.
Стефан Шазелас
Мне было трудно заставить это работать для меня, потому что я использовал другой разделитель. После RTFM я понял, что мне нужно начать регулярное выражение с \, а затем с разделителем.
Кристи
10
Как это относится ТОЛЬКО к первому матчу? Понятно, что он добавляет текст после матча, но откуда он знает только первый матч?
Мухаммед Нурельдин
7
Это добавляет текст после каждого матча, для меня.
Шоппенгауэр
49

Обратите внимание на стандартный sedсинтаксис (как в POSIX, который поддерживается всеми соответствующими sedреализациями (GNU, OS / X, BSD, Solaris ...)):

sed '/CLIENTSCRIPT=/a\
CLIENTSCRIPT2="hello"' file

Или на одной строке:

sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' file

( -expressions (и содержимое -fфайлов) объединяются с помощью новых строк для составления sedинтерпретаций сценария sed ).

-iВариант для редактирования на месте является также расширение GNU, некоторые другие реализации (например , во FreeBSD) поддержки -i ''для этого.

В качестве альтернативы для переносимости вы можете использовать perlвместо:

perl -pi -e '$_ .= qq(CLIENTSCRIPT2="hello"\n) if /CLIENTSCRIPT=/' file

Или вы можете использовать edили ex:

printf '%s\n' /CLIENTSCRIPT=/a 'CLIENTSCRIPT2="hello"' . w q | ex -s file
Стефан Шазелас
источник
2
Я могу ошибаться, но ток sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' fileэкранирует кавычку в конце первого параметра и прерывает команду.
заброшенная корзина
1
@AbandonedCart в оболочках Bourne cshили rcсемейства - '...'это сильные цитаты, внутри которых обратный слеш не является особенным. Единственное исключение, которое я знаю, это fishоболочка.
Стефан Шазелас
1
Терминал в MacOS (и впоследствии скрипт, запускаемый в терминале) также является, по-видимому, исключением. Я нашел альтернативный синтаксис, но все равно спасибо.
заброшенная корзина
1
@AbandonedCart, это что-то еще. Это macOS sedне POSIX-совместимый здесь. Это делает мое утверждение о том, что оно переносимо, неверно (для однострочного варианта). Я попрошу у opengroup подтверждение, действительно ли это несоответствие или неправильное толкование стандарта с моей стороны.
Стефан Шазелас
19

POSIX-совместимый с помощью sкоманды:

sed '/CLIENTSCRIPT="foo"/s/.*/&\
CLIENTSCRIPT2="hello"/' file
SLePort
источник
1
... который даже поддерживает вставку новых строк. Я люблю это!
Стефан Хеннингсен
1
Смотрите также, sed -e '/.../s/$/\' -e 'CLI.../'чтобы избежать проблем со строками, которые содержат последовательности байтов, не образующие допустимых символов.
Стефан Шазелас
15

Команда Sed, которая работает как на MacOS (по крайней мере, на OS 10), так и на Unix (т.е. не требует gnu sed, как это делает Gilles (в настоящее время принятая)):

sed -e '/CLIENTSCRIPT="foo"/a\'$'\n''CLIENTSCRIPT2="hello"' file

Это работает в bash и, возможно, в других оболочках, которые знают стиль цитаты оценки $ '\ n'. Все может быть в одной строке и работать в старых командах / POSIX sed. Если может быть несколько строк, соответствующих CLIENTSCRIPT = "foo" (или вашему аналогу), и вы хотите добавить только дополнительную строку в первый раз, вы можете переработать ее следующим образом:

sed -e '/^ *CLIENTSCRIPT="foo"/b ins' -e b -e ':ins' -e 'a\'$'\n''CLIENTSCRIPT2="hello"' -e ': done' -e 'n;b done' file

(это создает цикл после кода вставки строки, который циклически перебирает остальную часть файла, никогда не возвращаясь к первой команде sed).

Вы можете заметить, что я добавил '^ *' в соответствующий шаблон в случае, если эта строка появляется в комментарии, скажем, или имеет отступ. Это не на 100% идеально, но охватывает некоторые другие ситуации, которые могут быть распространены. Отрегулируйте как требуется ...

Эти два решения также решают проблему (для общего решения по добавлению строки), что если ваша новая вставленная строка содержит неэкранированные обратные косые черты или амперсанды, они будут интерпретироваться sed и, вероятно, не получатся такими же, как это \nесть, например.\0будет соответствовать первой строке. Особенно удобно, если вы добавляете строку, которая приходит из переменной, в противном случае вам пришлось бы сначала экранировать все, используя $ {var //} раньше, или другой оператор sed и т. Д.

Это решение немного менее запутанно в сценариях (хотя цитирование и \ n нелегко читать), когда вы не хотите помещать текст замены для команды a в начале строки, если, скажем, в функции с отступом линий. Я воспользовался тем, что оболочка $ '\ n' вычисляется до новой строки, а не в обычных одинарных кавычках '\ n'.

Это становится достаточно длинным, хотя я думаю, что perl / даже awk могут выиграть из-за большей читабельности.

Breezer
источник
1
Спасибо за ответ! Первый из них работает как шарм в ОС AIX.
Абхишек,
как вы это делаете, но для многострочного ввода?
Tatsu
1
POSIX sed поддерживает буквенные символы новой строки в строке замены, если вы «экранируете» их с помощью обратной косой черты ( источник ). Итак: s/CLIENTSCRIPT="foo"/&\ [Enter] CLIENTSCRIPT2="hello"/ . Обратная косая черта должна быть последним символом в строке (без пробела после нее), в отличие от того, как это выглядит в этом комментарии.
TheDudeAbides
1
@tatsu Newlines в замене строке s///, а также тех , кто в aи iкоманды могут быть «убежали» , используя тот же метод , я описываю в моем комментарии выше.
TheDudeAbides
4

Может быть, немного поздно, чтобы опубликовать ответ на этот вопрос, но я нашел некоторые из вышеупомянутых решений несколько громоздкими.

Я попробовал простую замену строк в sed, и это сработало:

sed 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

Знак & отражает соответствующую строку, а затем вы добавляете \ n и новую строку.

Как уже упоминалось, если вы хотите сделать это на месте:

sed -i 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

Еще одна вещь. Вы можете сопоставить, используя выражение:

sed -i 's/CLIENTSCRIPT=.*/&\nCLIENTSCRIPT2="hello"/' file

Надеюсь, это поможет кому-то

siwesam
источник
Это прекрасно работает в Linux, где GNU sed по умолчанию, потому что он понимает \nв строке замены. Plain-ваниль (POSIX) sedничего не понимаю \nв строке замены, однако, так что это не будет работать на BSD или MacOS-если вы не установлены GNU СЭД каким - то образом. С помощью vanilla sed вы можете встраивать новые строки, «экранируя» их, то есть заканчивая строку обратной косой чертой, а затем нажимая [Enter] ( ссылка под заголовком для [2addr]s/BRE/replacement/flags). Это означает, что ваша команда sed должна занимать несколько строк в терминале.
TheDudeAbides
Другим вариантом получения буквального перехода на новую строку в строке замены является использование функции цитирования ANSI-C в Bash, как упоминалось в одном из других ответов .
TheDudeAbides
1

У меня была похожая задача, и я не смог заставить работать вышеупомянутое решение Perl.

Вот мое решение:

perl -i -pe "BEGIN{undef $/;} s/^\[mysqld\]$/[mysqld]\n\ncollation-server = utf8_unicode_ci\n/sgm" /etc/mysql/my.cnf

Объяснение:

Использует регулярное выражение для поиска строки в моем файле /etc/mysql/my.cnf, которая содержит только [mysqld]и заменила ее

[mysqld] collation-server = utf8_unicode_ci

эффективно добавляя collation-server = utf8_unicode_ciстроку после строки, содержащей [mysqld].

Калеб
источник
0

Я должен был сделать это недавно и для Mac, и для Linux, и после просмотра множества постов и опробования многих вещей, по моему собственному мнению, я так и не достиг того, чего хотел: достаточно простое, чтобы понять решение с использованием хорошо известных и стандартные команды с простыми шаблонами, один вкладыш, переносимый, расширяемый, чтобы добавить больше ограничений. Затем я попытался взглянуть на это с другой точки зрения, и тогда я понял, что могу обойтись без опции «один вкладыш», если «2-вкладыш» соответствует остальным моим критериям. В конце я придумал это решение, которое мне нравится, и оно работает как в Ubuntu, так и в Mac, которым я хотел поделиться со всеми:

insertLine=$(( $(grep -n "foo" sample.txt | cut -f1 -d: | head -1) + 1 ))
sed -i -e "$insertLine"' i\'$'\n''bar'$'\n' sample.txt

В первой команде grep ищет номера строк, содержащие «foo», cut / head выбирает 1-е вхождение, а арифметическая операция увеличивает этот первый номер строки вхождения на 1, так как я хочу вставить после вхождения. Во второй команде это редактирование файла на месте, «i» для вставки: новая строка цитирования, «bar», затем еще одна новая строка. Результатом является добавление новой строки, содержащей "bar" после строки "foo". Каждая из этих 2 команд может быть расширена до более сложных операций и сопоставления.

momonari8
источник