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

В Вашей работе типа системного администратора действительно ли некоторые определяют задачу для Вас, любой озадачивает Вас (Как это работает?) или раздражают Вас (Разве, который не должен работать лучше/быстрее?)?

Найдите несколько из тех задач, определите очень простую функцию, которую Вы не понимаете, или это раздражает или озадачивает Вас. Попытайтесь реализовать озадачивающую, раздражающую или медленную опцию в C.

Вы получите более полное образование, если у Вас будет что-то практическая мотивация Вас, и у Вас будет останавливающаяся точка. Когда Вы реализовали свою очень простую опцию в C, можно остановить, оценить то, что Вы сделали, затем выберите другую задачу, которая все еще озадачивает Вас или раздражает Вас. В свете того, что Вы изучили, несколько задач будут теперь казаться отличающимися, чем они.

259
07.05.2015, 13:15
17 ответов

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

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

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

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


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                   
246
27.01.2020, 19:27
  • 1
    Стреляйте в меня теперь. Худший синтаксис когда-либо! –  Gili 20.05.2014, 22:51
  • 2
    Это - фантастическое объяснение, но я склонен согласиться с @Gili. –  gatoatigrado 21.05.2014, 23:44
  • 3
    Ваша шпаргалка имеет все это. –  konsolebox 19.07.2014, 15:30
  • 4
    Вам не нужна маркировка для использования 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 в замещающей строке не производит новую строку; Вам нужна обратная косая черта '\' сопровождаемый фактической новой строкой, чтобы сделать это. –  Wildcard 24.10.2015, 16:13
  • 5
    Обратите внимание, что тем синтаксисом является конкретный GNU (# команда, не разделенная от предыдущей, \n в RHS s). С GNU sed можно также использовать -z использовать NUL разграничило записи (и затем хлебайте в целом входе, если это - текст (который по определению не содержит NULs)). –  Stéphane Chazelas 11.08.2016, 09:33

Вы можете, но это трудно. Я рекомендую переключиться на другой инструмент. Если существует регулярное выражение, которое никогда не соответствует никакой части текста, который Вы хотите заменить, можно использовать его в качестве 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'
15
27.01.2020, 19:27
  • 1
    Как применить команду жемчуга к файлу? –  sebix 02.02.2016, 15:34
  • 2
    @sebix perl -0777 -pe '…' <input-file >output-file. Изменить файл на месте, perl -0777 -i -pe '…' filename –  Gilles 'SO- stop being evil' 02.02.2016, 15:56
  • 3
    См. также GNU sed -z опция (добавленный в 2012 после того, как тот ответ был отправлен): seq 10 | sed -z 's/4\n5/a\nb/'. –  Stéphane Chazelas 11.08.2016, 09:42

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

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

Что-то как:

/a test$/{
  N
  s/a test\nPlease do not/not a test\nBe/
}
39
27.01.2020, 19:27

Использовать 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 жемчуга причин для прихлебывания целых файлов. См. perldoc perlrun для обнаружения больше об этом.

190
27.01.2020, 19:27
  • 1
    Спасибо! Для многострочной работы, жемчуг без труда побеждает! Я закончил тем, что использовал 'жемчуг $ - пи-e 's/bar/baz /' fileA' для изменения оперативного файла. –  Nicholas Tolley Cottrell 04.02.2013, 16:36
  • 2
    Очень распространено, что исходный плакат просит sed и ответы с помощью awk или жемчуг появляются. Я думаю, что это не находится по теме, следовательно, извините, но я стрелял минус один. снижение конфетки –  Rho Phi 27.08.2015, 01:07
  • 3
    +1 и не соглашается с Roberto. Часто вопросы формулируются специально для незнания лучших методов. Когда нет независимого контекстного различия (как здесь), оптимальные решения должны получить, по крайней мере, столько же профиля сколько определенные для вопроса. –  geotheory 04.09.2015, 18:47
  • 4
    я думаю sed ответ выше доказывает, что ответ Perl находится по теме. –  reinierpost 24.11.2015, 12:46
  • 5
    Немного легче: С "-p0e" эти "-0777" не необходимо. unix.stackexchange.com/a/181215/197502 –  Weidenrinde 03.03.2017, 13:59

Думаю, лучше заменить символ \ 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 - окна, \ 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'
103
27.01.2020, 19:27

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

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

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 s в каждой строке копирует указанные строки ввода в sed ' s удерживаемое пространство . (Подумайте о произвольном текстовом буфере.)
  • После этого d отбрасывает каждую строку, чтобы предотвратить запись этих строк в вывод. Однако пространство хранения сохраняется.
  • Наконец, в самой последней строке g восстанавливает накопление каждой строки из удерживаемого пространства , так что sed может запускать свое регулярное выражение на весь ввод (а не построчно) и, следовательно, может соответствовать на \ n s.
44
27.01.2020, 19:27
sed -e'$!N;s/^\(a test\n\)Please do not be$/not \1Be/;P;D' <in >out

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

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

4
27.01.2020, 19:27

Это небольшая модификация умного ответа 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').

2
27.01.2020, 19:27

Я хотел добавить несколько строк 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
27.01.2020, 19:27
sed -i'.original' '/a test/,/Please do not/c not a test \nBe' alpha.txt

Здесь / тест /, / Пожалуйста, не / рассматривается как блок (многострочного) текста, c - это команда изменения , за которой следует новый текст , а не тест \ nBe

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

5
27.01.2020, 19:27

Я думаю, что это решение 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'
7
27.01.2020, 19:27

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

Сначала создайте какой-нибудь новый УНИКАЛЬНЫЙ разделитель строк, как вам нравится, например

$ 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 вашим и наоборот.)

5
27.01.2020, 19:27

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 в комментарии выше.

47
27.01.2020, 19:27

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

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

0
27.01.2020, 19:27

Хотяripgrepспециально не поддерживает встроенную замену , я обнаружил, что его текущая функциональность --replaceуже полезна для этого варианта использования и предпочтительнее использования sed, например.:

rg --replace $'not a test\nBe' --passthru --no-line-number \
--multiline 'a test\nPlease do not' alpha.txt > output.txt

Пояснение:

  • --replace 'string'включает режим замены и устанавливает строку замены. Можно включать захваченные группы регулярных выражений с помощью $1и т. д.
  • $'string'— это расширение Bash, так что \nстановится новой строкой для многострочной строки.
  • --passthruнеобходим, так как ripgrepобычно показывает только строки, соответствующие шаблону регулярного выражения. С этой опцией он также показывает все строки из файла, которые не совпадают.
  • --no-line-number / -Nпотому, что по умолчанию ripgrepвключает номера строк в вывод (, что полезно, когда отображаются только совпадающие строки ).
  • --multiline / -Uвключает многострочную обработку, которая отключена по умолчанию.
  • > output.txt, с опциями --passthroughи no-line-numberстандартный вывод соответствует желаемому новому файлу с заменами и может быть сохранен как обычно.
  • --multiline-dotallможно добавить дополнительно, если вы хотите использовать точку ('.' )шаблон регулярного выражения для соответствия новым строкам(\n).

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

1
21.06.2020, 16:45

Поскольку это уже объясняет большинство операций sed, я добавлю, как вы можете выполнять поиск внутри блока.

Предположим, вы хотите изменить xвнутри padding, но неoffset:

{
  padding: {
    x: 2,
    y: 0
  },
  offset: {
    x: 0,
    y: 1
  }
}

Сначала вы выбираете блок от padding: {до }

.
sed -r '/padding: \{/,/\}/ {
    # and inside the block you replace the value of x:
    s/^( +x:).*/\1 1,/
}'

Это также подходит для ответа на вопрос, хотя и не так элегантно, как пример JSON:

echo -e 'This is\na test\nPlease do not\nbe alarmed' | sed -r '
  /a test/,/Please do not/ {
    s/a test/not a test/
    s/Please do not/Be/
  }'
0
22.09.2020, 19:51

Расширяя блестящий принятый ответ Peter.O, если вы похожи на меня и вам нужно решение для замены более 2 строк за один раз, попробуйте это:

#!/bin/bash

pattern_1="<Directory \"\/var\/www\/cgi-bin\">"
pattern_2="[ ]*AllowOverride None\n"
pattern_3="[ ]*Options +ExecCGI\n"
pattern_4="[ ]*AddHandler cgi-script \.cgi \.pl\n"
pattern_5="[ ]*Require all granted\n"
pattern_6="<\/Directory>"

complete_pattern="$pattern_1\n$pattern_2$pattern_3$pattern_4$pattern_5$pattern_6"

replacement_1="#<Directory \"\/var\/www\/cgi-bin\">\n"
replacement_2="    #AllowOverride None\n"
replacement_3="    #Options +ExecCGI\n"
replacement_4="    #AddHandler cgi-script \.cgi \.pl\n"
replacement_5="    #Require all granted\n"
replacement_6="#<\/Directory>"

complete_replacement="$replacement_1$replacement_2$replacement_3$replacement_4$replacement_5$replacement_6"

filename="test.txt"

echo ""
echo "SEDding"
sed -i "/$pattern_1/{
    N;N;N;N;N
    s/$complete_pattern/$complete_replacement/
}" $filename

Пусть ваш входной файл будет:

#
#This is some test comments
#    Skip this
#

<Directory "/var/www/cgi-bin">
    AllowOverride None
    Options +ExecCGI
    AddHandler cgi-script.cgi.pl
    Require all granted
</Directory>

После запуска скрипта sed файл будет заменен в месте -на:

#
#This is some test comments
#    Skip this
#

#<Directory "/var/www/cgi-bin">
    #AllowOverride None
    #Options +ExecCGI
    #AddHandler cgi-script.cgi.pl
    #Require all granted
#</Directory>

Пояснение

  • pattern_1="<Directory \"\/var\/www\/cgi-bin\">"--Специальные символы должны быть экранированы обратной косой чертой \.

  • [ ]*--Это будет соответствовать 0 или многим пробелам. Стандартная нотация RegEx

  • sed -i "/$pattern_1/{--Будет выполнен поиск файла, строки -по строке -, для шаблона _1 [<Directory "/var/www/cgi-bin">]. Обратите внимание, что шаблон поиска НЕ ДОЛЖЕН СОДЕРЖАТЬ НОВУЮ СТРОКУ

    .

    Если и только если sed найдет $pattern_1в файле, тогда он продолжит выполнение подкода -в фигурных скобках {}. Он начнется со строки сопоставления с образцом в файле

    .
  • N;N;N;N;N--Nуказывает sed прочитать следующую строку после шаблона и присоединить ее к текущей строке. Важно понимать, что sed предназначен для замены только одной строки за раз, поэтому Nв основном заставит sed читать 2 строки и рассматривать их как одну строку с одной новой строкой \nмежду ними. Новая строка во второй строке будет игнорироваться. Объединяя 5 Ns, мы указываем sed читать 6 строк файла, начиная со строки сопоставления с образцом.

  • s/$complete_pattern/$complete_replacement/--Замените $complete_patternна $complete_replacement. Обратите внимание на наличие новых строк в переменных. Понимание этой части потребует проб и ошибок.

0
04.03.2021, 09:04

Теги

Похожие вопросы