Jak mogę użyć seda do zastąpienia ciągu wieloliniowego?

243

Zauważyłem, że jeśli dodam \ndo wzorca w celu podstawienia za pomocą sed, to nie pasuje. Przykład:

$ 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 mogę to uruchomić?

Belmin Fernandez
źródło
Sprytne obejście tutaj: unix.stackexchange.com/a/445666/61742 . Oczywiście to nie jest performatywne! Innymi dobrymi opcjami wykonania zamiany według twoich potrzeb mogą być awk, perl i python. Jest wiele innych, ale uważam, że awk jest najbardziej uniwersalny w różnych dystrybucjach Linuksa (na przykład). Dzięki!
Eduardo Lucio

Odpowiedzi:

235

W najprostszym wywołaniu sed ma on jedną linię tekstu w przestrzeni wzorów, tj. 1 wiersz \ntekstu oddzielonego od danych wejściowych. Pojedyncza linia w przestrzeni wzorów nie ma \n... To dlatego wyrażenie regularne niczego nie znajduje.

Możesz odczytywać wiele wierszy w przestrzeni wzorców i manipulować rzeczami zaskakująco dobrze, ale z większym niż zwykle wysiłkiem. Sed ma zestaw poleceń, które pozwalają na tego typu rzeczy ... Oto link do Podsumowania poleceń dla sed . Jest to najlepszy, jaki znalazłem i sprawił, że się potoczyłem.

Zapomnij jednak o idei „one-liner”, kiedy zaczniesz używać mikro-poleceń sed. Przydatne jest rozplanowanie go jak ustrukturyzowanego programu, dopóki go nie poczujesz ... Jest zaskakująco prosty i równie niezwykły. Możesz myśleć o tym jako o „języku asemblera” edycji tekstu.

Podsumowanie: Używaj sed do prostych rzeczy, a może nieco więcej, ale ogólnie rzecz biorąc, gdy wykracza to poza pracę z jedną linią, większość ludzi woli coś innego ...
Pozwolę komuś innemu zasugerować coś innego. naprawdę nie jestem pewien, jaki byłby najlepszy wybór (użyłbym sed, ale to dlatego, że nie znam wystarczająco dobrze perla).


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  

Tutaj jest ten sam skrypt, zagęszczony do tego, co oczywiście jest trudniejsze do czytania i pracy, ale niektórzy wątpliwie nazwaliby jedną linijkę

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

Oto moje polecenie „ściągawka”

:  # 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                   
Peter.O
źródło
167
Zastrzel mnie. Najgorsza składnia w historii!
Gili
53
To fantastyczne wytłumaczenie, ale jestem skłonny zgodzić się z @Gili.
gatoatigrado
11
Twój ściąg ma to wszystko.
konsolebox
3
Nie potrzebujesz etykiety, aby użyć ttutaj polecenia - gdy nie otrzyma etykiety, domyślnie rozgałęzia się do końca skryptu. Tak sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;t;P;D}}' alpha.txtrobi dokładnie to samo polecenie w każdych okolicznościach. Oczywiście dla tego konkretnego pliku sed '/test/{N;s/.*/not a test\nBe/}' alpha.txtrobi to samo, ale mój pierwszy przykład jest logicznie równoważny dla wszystkich możliwych plików. Zauważ też, że \nw ciągu zastępującym nie powstaje nowa linia; potrzebujesz do tego ukośnika odwrotnego `\ ', a po nim nowego wiersza.
Wildcard
9
Zauważ, że ta składnia jest specyficzna dla GNU ( #polecenie nie jest oddzielone od poprzedniego, \nw RHS z s). W GNU sedmożesz także używać -zrekordów rozdzielanych przez NUL (a następnie zamazywać całe wejście, jeśli jest to tekst (który z definicji nie zawiera NUL).
Stéphane Chazelas,
181

Użyj perlzamiast 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 -ejest standardową sekwencją wiersza poleceń „zamień na miejscu”, a -0777 powoduje, że perl cały plik jest w porządku. Zobacz perldoc perlrun, aby dowiedzieć się więcej na ten temat.

kodhead
źródło
3
Dzięki! W przypadku pracy wieloliniowej perl wygrywa! Skończyło się na tym, że użyłem `$ perl -pi -e 's / bar / baz /' fileA` do zmiany pliku na miejscu.
Nicholas Tolley Cottrell,
3
Bardzo często pyta się o oryginalny plakat sedi pojawiają się odpowiedzi przy użyciu awk lub perl. Myślę, że to nie jest na temat, dlatego przepraszam, ale zwolniłem jeden minus.
Rho Phi
68
+1 i nie zgadzam się z Roberto. Często pytania sformułowane specjalnie z powodu nieznajomości lepszych metod. Kiedy nie ma istotnej różnicy kontekstowej (jak tutaj), optymalne rozwiązania powinny uzyskać co najmniej tyle samo profilu, co rozwiązania specyficzne dla pytania.
geotheory
56
Myślę, że sedpowyższa odpowiedź dowodzi, że odpowiedź Perla dotyczy tematu.
reinierpost
7
Trochę łatwiej: z „-p0e” „-0777” nie jest konieczne. unix.stackexchange.com/a/181215/197502
Weidenrinde
96

Myślę, że lepiej jest zastąpić \nsymbol innym symbolem, a następnie działać jak zwykle:

np. nieobsługiwany kod źródłowy:

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

można zmienić na:

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

Jeśli ktoś nie wie, \nto kończy się linia UNIX, \r\n- Windows, \r- klasyczny Mac OS. Normalny tekst w systemie UNIX nie używa \rsymbolu, więc można go bezpiecznie używać w tym przypadku.

Możesz także użyć jakiegoś egzotycznego symbolu, aby tymczasowo zastąpić \ n. Na przykład - \ f (symbol wysuwu formularza). Możesz znaleźć więcej symboli tutaj .

cat alpha.txt | tr '\n' '\f' | sed -e 's/a test\fPlease do not/not a test\fBe/'  | tr '\f' '\n'
Xara
źródło
11
+1 za ten sprytny hack! Szczególnie przydatna jest rada dotycząca używania egzotycznego symbolu do tymczasowego zastąpienia nowej linii, chyba że masz absolutną pewność co do zawartości edytowanego pliku.
L0j1k
To nie działa tak jak napisane na OS X. Zamiast tego, trzeba wymienić wszystkie instancje \rw argumencie do sedz $(printf '\r').
abeboparebop
@abeboparebop: great find! 👍 alternatywnie zainstaluj GNU sed za pomocą homebrew: stackoverflow.com/a/30005262
ssc
@abeboparebop, W OSX, wystarczy dodać ciąg $przed sedem, aby zapobiec konwersji \rpliku na r. Krótki przykład: sed $'s/\r/~/'. Pełny przykład:cat alpha.txt | tr '\n' '\r' | sed $'s/a test\rPlease do not/not a test\rBe/' | tr '\r' '\n'
wisbucky
40

Biorąc wszystko pod uwagę, pożeranie całego pliku może być najszybszym sposobem.

Podstawowa składnia jest następująca:

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

Pamiętaj, że pożeranie całego pliku może nie być opcją, jeśli plik jest ogromnie duży. W takich przypadkach inne podane tutaj odpowiedzi oferują niestandardowe rozwiązania, które z pewnością będą działać na małej przestrzeni pamięci.

We wszystkich innych sytuacjach hackowania i slashowania, -e '1h;2,$H;$!d;g'po prostu poprzedzanie, po którym następuje oryginalny sedargument wyrażenia regularnego, prawie kończy zadanie.

na przykład

$ 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 ma -e '1h;2,$H;$!d;g'zrobić?

1, 2,$, $!Części są linia projektantom ten limit, który wyściełającej bezpośrednio następujące polecenie działa na.

  • 1: Tylko pierwsza linia
  • 2,$: Wszystkie linie zaczynające się od drugiej
  • $!: Każda linia inna niż ostatnia

Tak rozszerzone, to dzieje się w każdym wierszu wejścia linii N.

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

gKomenda nie została podana specyfikator linia, ale poprzedzające dkomenda ma specjalną klauzulę „ start następnego cyklu. ”, A to zapobiega guruchamianiu na wszystkich liniach oprócz ostatniego.

Co do znaczenia każdego polecenia:

  • Pierwszy hnastępnie Hs na każdej kopii wiersza powiedział linie wejściowe sed„s hold przestrzeni . (Pomyśl o dowolnym buforze tekstowym.)
  • Następnie dodrzuca każdą linię, aby zapobiec zapisaniu tych linii na wyjściu. Miejsce do przechowywania jest jednak zachowane.
  • Wreszcie, w ostatnim wierszu, gprzywraca akumulację każdej linii z przestrzeni wstrzymania, dzięki czemu sedjest w stanie uruchomić wyrażenie regularne na całym wejściu (a nie w sposób liniowy na raz), a zatem może dopasuj na \ns.
antak
źródło
38

sedposiada trzy polecenia do zarządzania działaniami multi-line: N, Di P(je porównać do normalnego n , di p).

W takim przypadku możesz dopasować pierwszy wiersz wzoru, użyć, Naby dołączyć drugi wiersz do obszaru wzorów, a następnie użyć, saby wykonać zamianę.

Coś jak:

/a test$/{
  N
  s/a test\nPlease do not/not a test\nBe/
}
andcoz
źródło
2
To jest niesamowite! Prostsze niż zaakceptowana odpowiedź i nadal skuteczne.
jeyk
I wszystkie te, obejmujące przestrzeń HOLD ( G, H, x...). Za pomocą spolecenia można również dodać więcej linii do obszaru wzorów .
Stéphane Chazelas,
to rozwiązanie nie działa w następującym przypadku „To jest \ na test \ na test \ n Proszę nie \ n niepokój się”
mug896
@ mug896 najprawdopodobniej potrzebujesz wielu Npoleceń
loa_in_
15

Możesz, ale to trudne . Polecam przejście na inne narzędzie. Jeśli istnieje wyrażenie regularne, które nigdy nie pasuje do żadnej części tekstu, który chcesz zastąpić, możesz użyć go jako separatora rekordów awk w GNU awk.

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

Jeśli nigdy nie ma dwóch kolejnych wierszy w ciągu wyszukiwania, możesz użyć „trybu akapitowego” awk (jeden lub więcej pustych wierszy oddzielnych rekordów).

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

Łatwym rozwiązaniem jest użycie Perla i pełne załadowanie pliku do pamięci.

perl -0777 -pe 's/hello/world/g'
Gilles
źródło
1
Jak zastosować polecenie perl do pliku?
sebix
2
@sebix perl -0777 -pe '…' <input-file >output-file. Aby zmodyfikować plik na miejscu,perl -0777 -i -pe '…' filename
Gilles
3
Zobacz także GNU sed„s -zopcję (dodane w 2012 roku po tym, że odpowiedź została wysłana) seq 10 | sed -z 's/4\n5/a\nb/'.
Stéphane Chazelas,
7

Myślę, że to sed rozwiązanie dla 2 linii dopasowania.

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

Jeśli chcesz dopasować 3 linie, to ...

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

Jeśli chcesz dopasować 4 linie, to ...

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

Jeśli część zamienna w poleceniu „s” zmniejsza linie, jest to nieco bardziej skomplikowane

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

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

Jeśli część zamieniająca rośnie w linie, jest to nieco bardziej skomplikowane

# 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'
mug896
źródło
To powinno dostać się na szczyt! Użyłem po prostu „-i” zamiast „-n” do zastąpienia dwóch linii, ponieważ tego właśnie potrzebuję, a nawiasem mówiąc, jest to również w przykładzie pytającego.
Nagev
5
sed -i'.original' '/a test/,/Please do not/c not a test \nBe' alpha.txt

Tutaj /a test/,/Please do not/jest uważany za blok tekstu (wieloliniowego), cjest to polecenie zmiany, po którym następuje nowy tekstnot a test \nBe

W przypadku, gdy tekst, który ma zostać zastąpiony, jest bardzo długi, sugerowałbym składnię ex .

gibies
źródło
Ups, problem polega na tym, że sed zastąpi cały tekst pomiędzy / a testem / i / Proszę nie / również ... :(
noonex
4
sed -e'$!N;s/^\(a test\n\)Please do not be$/not \1Be/;P;D' <in >out

Po prostu poszerz nieco okno przy wejściu.

To całkiem proste. Oprócz standardowej substytucji; trzeba tylko $!N, Pi Dtu.

mikeserv
źródło
4

Oprócz Perla, ogólne i przydatne podejście do edycji wieloliniowej dla strumieni (i plików również) to:

Najpierw utwórz nowy separator linii UNIQUE, jak chcesz, na przykład

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

Następnie w poleceniu sed (lub dowolnym innym narzędziu) zamieniasz \ n $ $ S, np

$ 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 zamienia separator linii ASCII na twój i odwrotnie.)

Gość
źródło
2

To jest mała modyfikacja sprytnej odpowiedzi Xary, aby działała na OS X (używam 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'

Zamiast jawnego używania \r, musisz użyć $(printf '\r').

abeboparebop
źródło
1
Chociaż printf '\r'(lub echo -e '\r') działają poprawnie, pamiętaj, że możesz po prostu użyć składni powłoki, $'\r'aby odwoływać się do liter dosuniętych. Na przykład, echo hi$'\n'thereecho nowej linii pomiędzy hii there. Podobnie możesz owinąć cały ciąg, aby każdy ukośnik \ uniknął kolejnego znaku:echo $'hi\nthere'
Dejay Clayton
1

Chciałem dodać kilka linii HTML do pliku za pomocą sed (i skończyłem tutaj). Zwykle używałbym perla, ale byłem na pudełku, które miało sed, bash i niewiele więcej. Odkryłem, że jeśli zmienię ciąg na jedną linię i pozwolę interpolować bash / sed, \ t \ n wszystko się ułoży:

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

Lepiej byłoby mieć funkcję pozwalającą uniknąć podwójnych cudzysłowów i ukośników, ale czasem abstrakcja jest złodziejem czasu.

Alexx Roche
źródło
1

GNU sedma -zopcję, która pozwala na użycie składni, którą OP próbował zastosować. ( strona podręcznika )

Przykład:

$ 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

Pamiętaj: jeśli używasz, ^a $teraz pasują do początku i końca linii rozdzielonych znakiem NUL (nie \n). I, aby upewnić się, że dopasowania we wszystkich ( \n-separatowanych) liniach są podstawione, nie zapomnij użyć gflagi do globalnych podstawień (np s/.../.../g.).


Kredyty: @ stéphane-chazelas po raz pierwszy wspomniał o -z w powyższym komentarzu.

Peterino
źródło
0

Sed przerywa wprowadzanie nowych linii. Utrzymuje tylko jedną linię na pętlę.
Dlatego nie ma możliwości dopasowania \n(nowej linii), jeśli przestrzeń wzorcowa go nie zawiera.

Istnieje jednak sposób, aby przy pomocy pętli sed mógł zachować dwie kolejne linie w przestrzeni wzorów:

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

Dodaj wymagane przetwarzanie między N i P (zastępując l).

W tym przypadku (2 linie):

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

Lub dla trzech linii:

$ 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

Zakłada się, że ta sama liczba linii zostanie zastąpiona.

Izaak
źródło