Как я могу использовать sed для замены многострочной строки?


243

Я заметил, что если я добавлю \nшаблон для замены с использованием sed, он не будет соответствовать. Пример:

$ cat > alpha.txt
This is
a test
Please do not
be alarmed

$ sed -i'.original' 's/a test\nPlease do not/not a test\nBe/' alpha.txt

$ diff alpha.txt{,.original}

$ # No differences printed out

Как я могу заставить это работать?


Умный обходной путь здесь: unix.stackexchange.com/a/445666/61742 . Конечно это не перформативно! Другими хорошими вариантами выполнения замены в соответствии с вашими потребностями могут быть awk, perl и python. Есть много других, но я считаю, что awk является наиболее универсальным в различных дистрибутивах Linux (например). Спасибо!
Эдуардо Лусио

Ответы:


235

В простейшем вызове sed он имеет одну строку текста в пространстве шаблона, т.е. 1 строка \nтекста с разделителями из ввода. У единственной строки в пространстве шаблонов нет \n... Вот почему ваше регулярное выражение ничего не находит.

Вы можете читать несколько строк в шаблонном пространстве и удивительно хорошо управлять вещами, но с усилиями, превышающими обычные. У Sed есть набор команд, которые позволяют такие вещи ... Вот ссылка на Сводку команд для sed , Это лучший, который я нашел, и заставил меня кататься.

Однако, если вы начнете использовать микро-команды sed, забудьте об идее «одной строки». Полезно выложить его как структурированную программу, пока не почувствуете это ... Это удивительно просто и не менее необычно. Вы можете думать об этом как о «языке ассемблера» редактирования текста.

Резюме: используйте sed для простых вещей, и, возможно, немного больше, но в целом, когда это выходит за рамки работы с одной строкой, большинство людей предпочитают что-то другое ...
Я позволю кому-то еще предложить что-то еще ... Я на самом деле не уверен, какой будет лучший выбор (я бы использовал sed, но это потому, что я недостаточно хорошо знаю Perl.)


sed '/^a test$/{
       $!{ N        # append the next line when not on the last line
         s/^a test\nPlease do not$/not a test\nBe/
                    # now test for a successful substitution, otherwise
                    #+  unpaired "a test" lines would be mis-handled
         t sub-yes  # branch_on_substitute (goto label :sub-yes)
         :sub-not   # a label (not essential; here to self document)
                    # if no substituion, print only the first line
         P          # pattern_first_line_print
         D          # pattern_ltrunc(line+nl)_top/cycle
         :sub-yes   # a label (the goto target of the 't' branch)
                    # fall through to final auto-pattern_print (2 lines)
       }    
     }' alpha.txt  

Здесь тот же сценарий, сжатый в то, что, очевидно, труднее читать и работать, но некоторые сомнительно назвали бы одну строку

sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;ty;P;D;:y}}' alpha.txt

Вот моя команда "шпаргалка"

:  # label
=  # line_number
a  # append_text_to_stdout_after_flush
b  # branch_unconditional             
c  # range_change                     
d  # pattern_delete_top/cycle          
D  # pattern_ltrunc(line+nl)_top/cycle 
g  # pattern=hold                      
G  # pattern+=nl+hold                  
h  # hold=pattern                      
H  # hold+=nl+pattern                  
i  # insert_text_to_stdout_now         
l  # pattern_list                       
n  # pattern_flush=nextline_continue   
N  # pattern+=nl+nextline              
p  # pattern_print                     
P  # pattern_first_line_print          
q  # flush_quit                        
r  # append_file_to_stdout_after_flush 
s  # substitute                                          
t  # branch_on_substitute              
w  # append_pattern_to_file_now         
x  # swap_pattern_and_hold             
y  # transform_chars                   

167
Стреляй в меня сейчас. Худший синтаксис когда-либо!
Гили

53
Это фантастическое объяснение, но я склонен согласиться с @Gili.
gatoatigrado

11
Ваша шпаргалка имеет все это.
konsolebox

3
Вам не нужна метка, чтобы использовать tкоманду здесь - если ей не дана метка, по умолчанию ветвление заканчивается в конце скрипта. То sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;t;P;D}}' alpha.txtже самое и ваша команда при любых обстоятельствах. Конечно, для этого конкретного файла, sed '/test/{N;s/.*/not a test\nBe/}' alpha.txtделает то же самое, но мой первый пример логически эквивалентен для всех возможных файлов. Также обратите внимание, что \nв строке замены не появляется символ новой строки; для этого вам понадобится обратная косая черта `\` с последующим символом новой строки.
Подстановочный

9
Обратите внимание, что этот синтаксис специфичен для GNU ( #команда не отделена от предыдущего \nв RHS of s). С GNU sedвы также можете использовать -zдля использования записей с разделителями NUL (а затем делать пометку во всем вводе, если это текст (который по определению не содержит NUL)).
Стефан Шазелас

181

Используйте perlвместо sed:

$ perl -0777 -i.original -pe 's/a test\nPlease do not/not a test\nBe/igs' alpha.txt
$ diff alpha.txt{,.original}
2,3c2,3
< not a test
< Be
---
> a test
> Please do not

-pi -eваша стандартная последовательность командной строки «заменить на месте», а -0777 заставляет perl отбрасывать файлы целиком. Смотрите perldoc perlrun, чтобы узнать больше об этом.


3
Спасибо! Для многострочной работы Perl выигрывает! В итоге я использовал `$ perl -pi -e 's / bar / baz /' fileA`, чтобы изменить файл на месте.
Николас Толи Коттрелл

3
Очень распространено, что оригинальный постер запрашивает sedи отвечает, используя awk или perl. Я думаю, что это не по теме, следовательно, извините, но я выстрелил минус один.
Ро Пхи

68
+1 и не согласен с Роберто. Часто вопросы формулируются специально для незнания лучших методов. Когда нет существенной контекстуальной разницы (как здесь), оптимальные решения должны иметь по крайней мере такой же профиль, как и конкретные вопросы.
geotheory

56
Я думаю, что sedответ выше доказывает, что Perl ответ по теме.
reinierpost

7
Немного проще: с "-p0e" "-0777" не требуется. unix.stackexchange.com/a/181215/197502
Weidenrinde

96

Я думаю, что лучше заменить \nсимвол другим символом, а затем работать как обычно:

например, нерабочий исходный код:

cat alpha.txt | sed -e 's/a test\nPlease do not/not a test\nBe/'

можно изменить на:

cat alpha.txt | tr '\n' '\r' | sed -e 's/a test\rPlease do not/not a test\rBe/'  | tr '\r' '\n'

Если кто-то не знает, \nконец строки UNIX, \r\n- windows, \r- классическая Mac OS. Обычный текст UNIX не использует \rсимвол, поэтому его можно использовать в этом случае.

Вы также можете использовать какой-нибудь экзотический символ для временной замены \ n. В качестве примера - \ f (символ перевода формы). Вы можете найти больше символов здесь .

cat alpha.txt | tr '\n' '\f' | sed -e 's/a test\fPlease do not/not a test\fBe/'  | tr '\f' '\n'

11
+1 за этот хитрый взлом! Особенно полезен совет по использованию экзотического символа для временной замены новой строки, если вы не абсолютно уверены в содержании редактируемого файла.
L0j1k

Это не работает так, как написано в OS X. Вместо этого нужно заменить все экземпляры \rаргумента на sedwith $(printf '\r').
abeboparebop

@abeboparebop: отличная находка! 👍 в качестве альтернативы, установите GNU sed, используя homebrew: stackoverflow.com/a/30005262
ssc

@abeboparebop, на OSX, вам просто нужно добавить $перед SED строки , чтобы предотвратить его преобразуя \rв r. Краткий пример: sed $'s/\r/~/'. Полный пример:cat alpha.txt | tr '\n' '\r' | sed $'s/a test\rPlease do not/not a test\rBe/' | tr '\r' '\n'
wisbucky

40

Учитывая все обстоятельства, поглощение всего файла может быть самым быстрым способом.

Основной синтаксис выглядит следующим образом:

sed -e '1h;2,$H;$!d;g' -e 's/__YOUR_REGEX_GOES_HERE__...'

Имейте в виду, поглощение всего файла может быть невозможным, если файл очень большой. Для таких случаев другие ответы, представленные здесь, предлагают индивидуальные решения, которые гарантированно будут работать с небольшим объемом памяти.

Для всех других ситуаций, связанных с хакерством и косой чертой, простое предварительное добавление, -e '1h;2,$H;$!d;g'за которым следует исходный sedаргумент регулярного выражения, в значительной степени делает свою работу

например

$ echo -e "Dog\nFox\nCat\nSnake\n" | sed -e '1h;2,$H;$!d;g' -re 's/([^\n]*)\n([^\n]*)\n/Quick \2\nLazy \1\n/g'
Quick Fox
Lazy Dog
Quick Snake
Lazy Cat

Что делает -e '1h;2,$H;$!d;g'?

1, 2,$, $!Часть линии спецификаторов этого предел , который выстилает непосредственно следующая команда работает на.

  • 1: Только первая строка
  • 2,$: Все строки, начиная со второго
  • $!: Каждая строка, кроме последней

Таким образом, это то, что происходит в каждой строке ввода N строк.

  1: h, d
  2: H, d
  3: H, d
  .
  .
N-2: H, d
N-1: H, d
  N: H, g

gКоманда не дала строки спецификатора, но предшествующая dкоманда имеет специальное положение « Пуск следующий цикл. », И это предотвращает gзапуск на все линии , кроме последнего.

Что касается значения каждой команды:

  • Первый hследуют Hс на каждой линии копий сказал линии ввода в sed«ы трюма . (Вспомните произвольный текстовый буфер.)
  • После этого dотбрасывает каждую строку для предотвращения записи этих строк в вывод. Держать пространство , однако сохраняется.
  • Наконец, в самой последней строке gвосстанавливает накопление каждой строки из пространства удержания, чтобы sedона могла выполнять свое регулярное выражение на всем входе (а не в виде строки за раз) и, следовательно, могла совпадение по \nс.

38

sedимеет три команды для управления операциями многострочных: N, Dи P(сравнить их нормальные n , dа p).

В этом случае вы можете сопоставить первую строку вашего шаблона, использовать, Nчтобы добавить вторую строку к пространству шаблона, а затем использовать sдля выполнения замены.

Что-то вроде:

/a test$/{
  N
  s/a test\nPlease do not/not a test\nBe/
}

2
Это круто! Проще чем принятый ответ и все еще эффективен.
Jeyk

И все те , вовлекающие трюм ( G, H, x...). С помощью sкоманды также можно добавить больше строк в пространство шаблона .
Стефан Шазелас

Добавлена ​​ссылка на ссылку на команду sed, отдельная спецификация unix v2, 1997 .
n611x007

Это решение не работает со следующим случаем «Это \ n тест \ n тест \ n Пожалуйста, не \ n будьте встревожены»
mug896

@ mug896 вам, скорее всего, понадобится несколько Nкоманд
loa_in_

15

Вы можете, но это сложно . Я рекомендую перейти на другой инструмент. Если есть регулярное выражение, которое никогда не соответствует какой-либо части текста, которую вы хотите заменить, вы можете использовать его как разделитель записей awk в GNU awk.

awk -v RS='a' '{gsub(/hello/, "world"); print}'

Если в строке поиска никогда не бывает двух последовательных символов новой строки, вы можете использовать «режим абзаца» в awk (одна или несколько пустых строк разделяют записи).

awk -v RS='' '{gsub(/hello/, "world"); print}'

Простое решение - использовать Perl и полностью загрузить файл в память.

perl -0777 -pe 's/hello/world/g'

1
Как применить команду perl к файлу?
Sebix

2
@sebix perl -0777 -pe '…' <input-file >output-file. Чтобы изменить файл на месте,perl -0777 -i -pe '…' filename
Жиль

3
Смотрите также GNU sed«s -zвариант (добавлена в 2012 году после того, как этот ответ был отправлен): seq 10 | sed -z 's/4\n5/a\nb/'.
Стефан Шазелас

7

Я думаю, что это решение Sed для соответствия двух строк.

sed -n '$!N;s@a test\nPlease do not@not a test\nBe@;P;D' alpha.txt

Если вы хотите, чтобы 3 строки соответствовали, то ...

sed -n '1{$!N};$!N;s@aaa\nbbb\nccc@xxx\nyyy\nzzz@;P;D'

Если вы хотите, чтобы 4 строки соответствовали, то ...

sed -n '1{$!N;$!N};$!N;s@ ... @ ... @;P;D'

Если заменяющая часть в команде "s" сжимает строки, то немного сложнее, как это

# aaa\nbbb\nccc shrink to one line "xxx"

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@xxx@;$!N;$!N};P;D'

Если у части повторного роста растут линии, то немного сложнее, как это

# aaa\nbbb\nccc grow to five lines vvv\nwww\nxxx\nyyy\nzzz

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@vvv\nwww\nxxx\nyyy\nzzz@;P;s/.*\n//M;P;s/.*\n//M};P;D'

Это должно пробиться на вершину! Я просто использовал «-i» вместо «-n» для подстановки в две строки, потому что это то, что мне нужно, и, кстати, это тоже в примере Аскера.
Нагев

5
sed -i'.original' '/a test/,/Please do not/c not a test \nBe' alpha.txt

Здесь /a test/,/Please do not/рассматривается как блок из (многострочного) текста, cэто команда изменения с последующим новым текстомnot a test \nBe

В случае, если текст для замены очень длинный, я бы предложил ex синтаксис.


Ой, проблема в том, что sed заменит весь возможный текст между / a test / и / Пожалуйста, не / также ... :(
noonex

4
sed -e'$!N;s/^\(a test\n\)Please do not be$/not \1Be/;P;D' <in >out

Просто немного расширите окно ввода.

Это довольно легко. Помимо стандартной замены; вам нужно только $!N, Pи Dздесь.


4

Помимо Perl, общий и удобный подход для многострочного редактирования потоков (и файлов тоже):

Сначала создайте новый UNIQUE разделитель строк, как вам нравится, например

$ S=__ABC__                     # simple
$ S=__$RANDOM$RANDOM$RANDOM__   # better
$ S=$(openssl rand -hex 16)     # ultimate

Затем в вашей команде sed (или любом другом инструменте) вы заменяете \ n на $ {S}, например

$ cat file.txt | awk 1 ORS=$S |  sed -e "s/a test${S}Please do not/not a test\nBe/" | awk 1 RS=$S > file_new.txt

(awk заменяет разделитель строк ASCII на ваш и наоборот.)


2

Это небольшая модификация умного ответа xara, чтобы он работал на OS X (я использую 10.10):

cat alpha.txt | tr '\n' '\r' | sed -e 's/a test$(printf '\r')Please do not/not a test$(printf '\r')Be/'  | tr '\r' '\n'

Вместо того, чтобы явно использовать \r, вы должны использовать $(printf '\r').


1
Хотя printf '\r'(или echo -e '\r') все работает правильно, обратите внимание, что вы можете просто использовать синтаксис оболочки $'\r'для ссылки на экранированные литералы. Например, echo hi$'\n'thereбудет эхо новой строки между hiи there. Точно так же вы можете обернуть всю строку так, чтобы каждая обратная косая черта \ экранировала свой последующий символ:echo $'hi\nthere'
Dejay Clayton

1

Я хотел добавить несколько строк HTML в файл, используя sed (и закончил здесь). Обычно я бы просто использовал Perl, но я был на коробке с sed, bash и многим другим. Я обнаружил, что если я изменил строку на одну строку и позволил bash / sed интерполировать \ t \ n, все получилось:

HTML_FILE='a.html' #contains an anchor in the form <a name="nchor" />
BASH_STRING_A='apples'
BASH_STRING_B='bananas'
INSERT="\t<li>$BASH_STRING_A<\/li>\n\t<li>$BASH_STRING_B<\/li>\n<a name=\"nchor\"\/>"
sed -i "s/<a name=\"nchor"\/>/$INSERT/" $HTML_FILE

Было бы чётче иметь функцию для избежания двойных кавычек и косой черты, но иногда абстракция - это вор времени.


1

В GNU sedесть -zопция, позволяющая использовать синтаксис, который пытался применить OP. ( справочная страница )

Пример:

$ cat alpha.txt
This is
a test
Please do not
be alarmed
$ sed -z 's/a test\nPlease do not\nbe/not a test\nBe/' -i alpha.txt
$ cat alpha.txt
This is
not a test
Be alarmed

Помните: если вы используете, ^и $теперь они соответствуют началу и концу строк, разделенных символом NUL (не \n). И, чтобы гарантировать, что совпадения во всех ваших ( \n-отделенных) строках заменены, не забудьте использовать gфлаг для глобальных замен (например s/.../.../g).


Кредиты: @ stéphane-chazelas впервые упомянул -z в комментарии выше.


0

Sed ломает ввод на новых строках. Он сохраняет только одну строку в цикле.
Поэтому нет способа сопоставить \n(новую строку), если пространство шаблона не содержит его.

Однако есть способ заставить sed хранить две последовательные строки в пространстве шаблона с помощью цикла:

sed 'N;l;P;D' alpha.txt

Добавьте любую обработку, необходимую между N и P (заменив l).

В этом случае (2 строки):

$ sed 'N;s/a test\nPlease do not/not a test\nBe/;P;D' alpha.txt
This is
not a test
Be
be alarmed

Или для трех строк:

$ sed -n '1{$!N};$!N;s@a test\nPlease do not\nbe@not a test\nDo\nBe@;P;D' alpha.txt 
This is
not a test
Do
Be alarmed

Это при условии, что такое же количество строк будет заменено.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.