Як я можу використовувати 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, але це тому, що я не знаю перл досить добре.)


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в рядку заміни не утворюється новий рядок; для цього вам потрібен зворотний нахил `\`, а за ним фактичний новий рядок.
Wildcard

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 спричиняє перл на цілий сервер файлів. Дивіться perldoc perlrun, щоб дізнатися більше про нього.


3
Дякую! Для багатолінійної роботи perl виграє руки вниз! Я змінив файл на місці за допомогою $ $ perl -pi -e 's / bar / baz /' fileA`.
Ніколас Толлі Коттрелл

3
Дуже часто зустрічається оригінальний плакат, який запитує sedі відповідає, використовуючи awk або perl. Я думаю, це не на тему, отже, вибачте, але я випустив мінус один.
Rho Phi

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

56
Я думаю, що sedвідповідь вище доводить, що відповідь Perl є темою.
reinierpost

7
Трохи простіше: "-p0e" не потрібен "-0777". unix.stackexchange.com/a/181215/197502
Вайденрінде

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в аргументі sedз $(printf '\r').
abeboparebop

@abeboparebop: чудова знахідка! 👍 або, встановіть GNU sed за допомогою домашньої мови: stackoverflow.com/a/30005262
ssc

@abeboparebop, На OSX вам просто потрібно додати a $перед рядком sed, щоб запобігти його перетворенню в \ran 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
заважайте

@ 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 для 2 рядків.

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

У випадку, якщо текст, що підлягає заміні, дуже довгий, я б запропонував колишній синтаксис.


Ой, проблема полягає в тому, що 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'
косий рядок отримав

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опція, яка дозволяє використовувати синтаксис, який намагався застосувати ОП. ( чоловіча сторінка )

Приклад:

$ 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.