it-swarm-eu.dev

Jak mohu použít sed k nahrazení víceřádkového řetězce?

Všiml jsem si toho, když přidám \n do vzoru pro nahrazení pomocí sed, neodpovídá. Příklad:

$ 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

Jak to dokážu?

274
Belmin Fernandez

V nejjednodušším volání sed jeden řádek textu v vzorový prostor, tj. 1 řádek \n oddělený text ze vstupu. Jeden řádek ve vzorovém prostoru nemá \n... Proto váš regex nic nenašel.

Do prostoru vzorů můžete číst více řádků a překvapivě dobře manipulovat s věcmi, ale s více než obvyklým úsilím .. Sed má sadu příkazů, které umožňují tento typ věcí ... Zde je odkaz na Shrnutí příkazů pro sed . Je to ten nejlepší, co jsem našel, a přinutil mě kroutit.

Jakmile však začnete používat mikro příkazy sedu, zapomeňte na myšlenku „one-liner“. Je užitečné rozvinout to jako strukturovaný program, dokud ho necítíte ... Je to překvapivě jednoduché a stejně neobvyklé. Dalo by se to považovat za „jazyk assembleru“ pro editaci textu.

Shrnutí: Používejte sed pro jednoduché věci, a možná trochu víc, ale obecně, když se to dostane nad rámec práce s jedním řádkem, většina lidí dává přednost něčemu jinému ...
Nechám někoho jiného, ​​aby navrhl něco jiného .. Opravdu si nejsem jistý, jaká by byla nejlepší volba (použil bych sed, ale to proto, že neznám Perla dost dobře.)


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  

Tady je to stejný skript, zhuštěný do toho, co je zjevně těžší číst a pracovat s ním, ale někteří by pochybně nazvali jednostrannou

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

Zde je můj příkaz "cheat-sheet"

:  # 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                   
255
Peter.O

Místo Perl použijte 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 je vaše standardní sekvence příkazového řádku "nahradit na místě" a -0777 způsobí, že soubory Perl Slurp budou celé. Více o tom najdete na perldoc perlrun .

196
codehead

Myslím, že je lepší nahradit symbol \n Jiným symbolem a poté pracovat jako obvykle:

např. nespracovaný zdrojový kód:

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

lze změnit na:

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

Pokud někdo neví, \n Končí v UNIXovém řádku, \r\n - okna, \r - klasický Mac OS. Normální text v systému UNIX nepoužívá symbol \r, Takže je bezpečné jej v tomto případě použít.

Můžete také použít nějaký exotický symbol pro dočasné nahrazení\n. Jako příklad -\f (symbol zdroje formuláře). Můžete najít více symbolů zde .

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

Všechny zvažované věci pohlcují celý soubor může být nejrychlejší způsob, jak jít.

Základní syntaxe je následující:

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

Nezapomínejme, že pohrávání s celým souborem nemusí být možné, pokud je soubor nesmírně velký. V takových případech nabízejí další odpovědi zde přizpůsobená řešení, u nichž je zaručeno, že budou pracovat na malé paměti.

U všech ostatních situací hackování a lomítka stačí předinstalovat -e '1h;2,$H;$!d;g' následovaný vaším původním argumentem sed regex do značné míry splní svou úlohu.

např.

$ 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

Co dělá -e '1h;2,$H;$!d;g' dělat?

The 1, 2,$, $! parts jsou specifikátory řádků, které omezují, na jakých řádcích se spustí přímo následující příkaz.

  • 1: Pouze první řádek
  • 2,$: Všechny řádky začínající od druhé
  • $!: Každý řádek jiný než poslední

Tak rozšířeno, to se děje na každém řádku vstupu N linky.

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

Příkaz g nemá specifikátor řádku, ale předchozí příkaz d má speciální klauzuli " Zahájit další cyklus.", a to zabraňuje g od běhu na všech řádcích kromě posledního.

Pokud jde o význam každého příkazu:

  • První h následovaný Hs na každém řádku zkopíruje uvedené řádky vstupu do sed 's držte mezeru. (Přemýšlejte o libovolném textovém bufferu.)
  • Poté d zahodí každý řádek, aby zabránil zápisu těchto řádků do výstupu. hold space však zůstane zachován.
  • Nakonec na posledním řádku g obnoví akumulaci každého řádku z hold space, takže sed je schopen spustit svůj regex jako celek vstup (spíše než line-at-time způsobem), a proto je schopen odpovídat na \ns.
52
antak

sed má tři příkazy pro správu víceřádkových operací: N , D a P (porovnejte je s normálnín, d a p).

V tomto případě můžete porovnat první řádek vzoru, pomocí N připojit druhý řádek k vzorový prostor a poté pomocí s provést svůj substituce.

Něco jako:

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

Můžete, ale je to těžké . Doporučuji přejít na jiný nástroj. Pokud existuje regulární výraz, který nikdy neodpovídá žádné části textu, který chcete nahradit, můžete jej použít jako oddělovač záznamu awk v GNU awk).

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

Pokud ve vyhledávacím řetězci nejsou nikdy dva po sobě jdoucí řádky, můžete použít awkův „odstavcový režim“ (jeden nebo více samostatných záznamů prázdných řádků).

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

Snadným řešením je použít Perl a plně načíst soubor do paměti.

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

Myslím, že toto je sed řešení pro 2 řádky.

sed -n '$!N;[email protected] test\nPlease do [email protected] a test\[email protected];P;D' alpha.txt

Pokud chcete 3 řádky odpovídající ...

sed -n '1{$!N};$!N;[email protected]\nbbb\[email protected]\nyyy\[email protected];P;D'

Pokud chcete, aby se shodovaly 4 řádky, ...

sed -n '1{$!N;$!N};$!N;[email protected] ... @ ... @;P;D'

Pokud náhradní díl v příkazu „s“ zmenšuje čáry, pak je o něco složitější

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

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{[email protected]@[email protected];$!N;$!N};P;D'

Pokud část repacementu roste, pak je to trochu komplikovanější

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

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

tato druhá metoda je jednoduchá doslovná náhrada pro kopírování a vkládání doslovných textových souborů s malou velikostí (potřebujete skriptový soubor Shell)

#!/bin/bash

# copy & paste content that you want to substitute

AA=$( cat <<\EOF | sed -z -e 's#\([][^$*\.#]\)#\\\1#g' -e 's#\n#\\n#g'
a test
Please do not
EOF
)

BB=$( cat <<\EOF | sed -z -e 's#\([&\#]\)#\\\1#g' -e 's#\n#\\n#g'
not a test
Be
EOF
)

sed -z -i 's#'"${AA}"'#'"${BB}"'#g' *.txt   # apply to all *.txt files
10
mug896

GNU sed-z možnost, která umožňuje použít syntaxi, kterou se OP pokusil použít. ( man page )

Příklad:

$ 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

Buďte si vědomi: Pokud používáte ^ a $ nyní odpovídají začátku a konci řádků oddělených znakem NUL (ne \n). A pro zajištění shody všech vašich (\n- oddělené) řádky jsou nahrazeny, nezapomeňte použít příznak g pro globální substituce (např. s/.../.../g).


Kredity: @ stéphane-chazelas první zmínka -z v komentáři výše.

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

Tady /a test/,/Please do not/ je považováno za blok textu (víceřádkový), c je příkaz change následovaný novým textem not a test \nBe

V případě, že text, který má být nahrazen, je velmi dlouhý, navrhl bych ex syntaxi.

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

Stačí trochu rozšířit okno na vstupu.

Je to docela snadné. Kromě standardní náhrady; potřebujete pouze $!N, P a D zde.

4
mikeserv

Kromě Perlu je obecný a šikovný přístup pro víceřádkové úpravy datových proudů (a také souborů):

Nejprve vytvořte například nový oddělovač UNIQUE, jak se vám líbí

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

Poté ve vašem příkazu sed (nebo jiném nástroji) nahradíte\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 nahradí ASCII oddělovač řádků vaším a naopak).

4
guest

Toto je malá modifikace chytré odpovědi xary, aby fungovala na OS X (používám 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'

Místo explicitního použití \r Musíte použít $(printf '\r').

2
abeboparebop

Chtěl jsem přidat několik řádků HTML do souboru pomocí sed, (a skončil tady). Normálně bych jen používal Perla, ale byl jsem na krabici, která měla sed, bash a nic jiného. Zjistil jsem, že kdybych změnil řetězec na jediný řádek a nechal bash/sed interpolovat,\t\n všechno dopadlo:

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

Bylo by čistší mít funkci uniknout dvojitým uvozovkám a lomítkům, ale někdy je abstrakce zloděj času.

1
Alexx Roche

Sed přeruší vstup na nové řádky. Udržuje pouze jeden řádek na smyčku.
Proto neexistuje způsob, jak porovnat \n (nový řádek), pokud jej vzorový prostor neobsahuje.

Existuje však způsob, jak si můžete udržet sed keep dvě po sobě jdoucí řádky ve vzorovém prostoru pomocí smyčky:

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

Přidejte jakékoli potřebné zpracování mezi N a P (nahrazení l).

V tomto případě (2 řádky):

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

Nebo pro tři řádky:

$ sed -n '1{$!N};$!N;[email protected] test\nPlease do not\[email protected] a test\nDo\[email protected];P;D' alpha.txt 
This is
not a test
Do
Be alarmed

To je za předpokladu, že bude nahrazeno stejné množství řádků.

0
Isaac