Zamień ciąg zawierający znak nowej linii w dużym pliku

16

Czy ktoś wie o nieliniowym narzędziu służącym do „binarnego” wyszukiwania / zastępowania ciągów w sposób zapewniający oszczędność pamięci? Zobacz też to pytanie .

Mam plik tekstowy + 2 GB, który chciałbym przetworzyć podobnie do tego, co wygląda na to, że:

sed -e 's/>\n/>/g'

Oznacza to, że chcę usunąć wszystkie nowe wiersze, które występują po >, ale nigdzie indziej, więc to wyklucza tr -d.

To polecenie (otrzymane z odpowiedzi na podobne pytanie ) kończy się niepowodzeniem couldn't re-allocate memory:

sed --unbuffered ':a;N;$!ba;s/>\n/>/g'

Czy są jakieś inne metody bez uciekania się do C? Nienawidzę Perla, ale jestem gotów zrobić wyjątek w tym przypadku :-)

Nie wiem na pewno żadnego znaku, który nie występuje w danych, więc tymczasowe zastąpienie \ngo innym znakiem jest czymś, czego chciałbym uniknąć, jeśli to możliwe.

Jakieś dobre pomysły, ktoś?

MattBianco
źródło
Próbowałeś już opcji --unbuffered?
ctrl-alt-delor
--unbuffered
Brakuje
Co ma $!zrobić?
ctrl-alt-delor
Co jest nie tak z pierwszym poleceniem sed. Drugi wydaje się czytać wszystko w przestrzeni wzorów, ale nie wiem, czy tak $!jest. Spodziewam się, że będzie to wymagało dużo pamięci.
ctrl-alt-delor
Problem polega na tym, że sed odczytuje wszystko jako linie, dlatego pierwsze polecenie nie usuwa nowych linii, ponieważ wypisuje tekst wiersz po wierszu. Drugie polecenie to tylko obejście. Myślę, że sednie jest właściwym narzędziem w tym przypadku.
MattBianco

Odpowiedzi:

14

To naprawdę jest banalne w Perlu, nie powinieneś tego nienawidzić!

perl -i.bak -pe 's/>\n/>/' file

Wyjaśnienie

  • -i: edytuj plik na miejscu i utwórz kopię zapasową oryginału o nazwie file.bak. Jeśli nie chcesz kopii zapasowej, po prostu użyj jej perl -i -pe.
  • -pe: odczytaj plik wejściowy linia po linii i wydrukuj każdą linię po zastosowaniu skryptu podanego jako -e.
  • s/>\n/>/: podstawienie, tak jak sed.

A oto awkpodejście:

awk  '{if(/>$/){printf "%s",$0}else{print}}' file2 
terdon
źródło
3
+1. awk golf:awk '{ORS=/>$/?"":"\n"}1'
glenn jackman
1
Dlaczego ogólnie nie lubię perla to ten sam powód, dla którego wybrałem tę odpowiedź (a właściwie twój komentarz do odpowiedzi Gnouca): czytelność. Używanie perla -pe z prostym „wzorem sed” jest o wiele bardziej czytelne niż złożone wyrażenie sed.
MattBianco
3
@MattBianco jest wystarczająco sprawiedliwe, ale dla pewności nie ma to nic wspólnego z Perlem. Wygląd używany przez Gnouc jest cechą niektórych języków wyrażeń regularnych (w tym między innymi PCRE), a nie winą Perla. Ponadto, po uwzględnieniu tej sednej potworności ':a;N;$!ba;s/>\n/>/g'w swoim pytaniu, zrzekłeś się prawa do narzekań na czytelność! : P
terdon
@glennjackman nice! Bawiłem się foo ? bar : bazkonstruktem, ale nie mogłem go uruchomić.
terdon
@terdon: Tak, mój błąd. Usuń to.
cuonglm
7

perlRozwiązanie:

$ perl -pe 's/(?<=>)\n//'

Wyjaśnienie

  • s/// służy do podstawienia łańcucha.
  • (?<=>) jest wyglądający za wzór.
  • \n pasuje do nowej linii.

Cały wzorzec znaczeń usuwa wszystkie znaki nowej linii, które miały >przed nim.

Cuonglm
źródło
2
chcesz skomentować, co robi części programu? Zawsze chcę się uczyć.
MattBianco
2
Po co zawracać sobie głowę wyglądem? Dlaczego nie tylko s/>\n/>/?
terdon
1
lub s/>\K\n//też działałby
glenn jackman
@terdon: Tylko pierwszą rzeczą, którą wymyśliłem, zamiast usunąć
cuonglm,
@glennjackman: good point!
cuonglm
3

Co powiesz na to:

sed ':loop
  />$/ { N
    s/\n//
    b loop
  }' file

W przypadku GNU sed możesz również spróbować dodać opcję -u( --unbuffered) zgodnie z pytaniem. GNU sed jest również zadowolony z tego, że jest to prosta liniówka:

sed ':loop />$/ { N; s/\n//; b loop }' file
Graeme
źródło
To nie usuwa ostatniego, \njeśli plik się kończy >\n, ale prawdopodobnie i tak jest preferowane.
Stéphane Chazelas
@ StéphaneChazelas, dlaczego zamknięcie }musi być w osobnym wyrażeniu? czy to nie zadziała jako wyrażenie wielowierszowe?
Graeme
1
Działa to w septach POSIX z b loop\n}lub, -e 'b loop' -e '}'ale nie jako b loop;}i na pewno nie b loop}dlatego, że }i ;są poprawne w nazwach etykiet (chociaż nikt przy zdrowych zmysłach nie użyłby go. A to oznacza, że ​​GNU sed nie jest zgodny z POSIX) i }polecenie należy rozdzielić z bpolecenia.
Stéphane Chazelas
@ StéphaneChazelas, GNU sedjest zadowolony ze wszystkich powyższych, nawet z --posix! Standard zawiera również następujące wyrażenia nawiasów klamrowych - The list of sed functions shall be surrounded by braces and separated by <newline>s. Czy to nie znaczy, że średników należy używać tylko poza nawiasami klamrowymi?
Graeme
@mikeserv, pętla jest potrzebna do obsługi kolejnych linii kończących się na >. Oryginał nigdy go nie miał, zauważył to Stéphane.
Graeme
1

Powinieneś być w stanie używać sedz Npoleceniem, ale sztuczka polega na usunięciu jednej linii z obszaru wzorca za każdym razem, gdy dodajesz kolejny (tak, że obszar wzorca zawsze zawiera tylko 2 kolejne wiersze, zamiast próbować czytać w całości plik) - spróbuj

sed ':a;$!N;s/>\n/>/;P;D;ba'

EDIT: Po ponownym przeczytaniu Pēteris Krumins' Famous sed jednej wkładki Poradnik wierzę lepszym sedrozwiązaniem byłoby

sed -e :a -e '/>$/N; s/\n//; ta'

która dołącza tylko następującą linię w przypadku, gdy jest już >dopasowana na końcu i powinna warunkowo zapętlić się z powrotem, aby obsłużyć przypadek kolejnych pasujących linii (jest to Krumin 39. Dołącz linię do następnej, jeśli kończy się odwrotnym ukośnikiem „\” dokładnie z wyjątkiem zamiany >na \jako znak łączenia oraz faktu, że znak łączenia jest zachowany na wyjściu.

steeldriver
źródło
2
To nie działa, jeśli kończą się 2 kolejne linie >(dotyczy to również GNU)
Stéphane Chazelas
1

sednie zapewnia sposobu emitowania wyjścia bez końcowego nowego wiersza. Twoje podejście przy użyciu Nzasadniczo działa, ale zapisuje niekompletne linie w pamięci, a zatem może się nie powieść, jeśli linie staną się zbyt długie (implanty sed zwykle nie są zaprojektowane do obsługi bardzo długich linii).

Zamiast tego możesz użyć awk.

awk '{if (/<$/) printf "%s", $0; else print}'

Alternatywnym podejściem jest trzamiana znaku nowej linii na „nudny”, często występujący znak. Przestrzeń może tu działać - wybierz znak, który ma tendencję do pojawiania się w każdej linii lub co najmniej dużej części linii w twoich danych.

tr ' \n' '\n ' | sed 's/> />/g' | tr '\n ' ' \n'
Gilles „SO- przestań być zły”
źródło
Obie metody zostały już tutaj wykazane, aby uzyskać lepszy efekt w innych odpowiedziach. Jego podejście sednie działa bez bufora 2,5 gigabajta.
mikeserv
Czy ktoś wspomniał o awk? Och, tęskniłem za tym, z jakiegoś powodu zauważyłem perla w odpowiedzi terdona. Nikt nie wspominał o trpodejściu - mikeserv, opublikowałeś inne (prawidłowe, ale mniej ogólne) podejście, które również się stosuje tr.
Gilles „SO - przestań być zły”,
prawidłowe, ale mniej ogólne dźwięki dla mnie, tak jak właśnie nazwałeś to działającym, ukierunkowanym rozwiązaniem. myślę, że trudno argumentować, że coś takiego nie jest użyteczne, co jest dziwne, ponieważ ma 0 głosów pozytywnych. Największą różnicą , jaką widzę między moim własnym rozwiązaniem a twoją bardziej ogólną ofertą, jest to, że moje konkretnie rozwiązuje problem, podczas gdy twoje może ogólnie. To może sprawić, że opłaca się - a może nawet odwrócę mój głos - ale jest też irytująca kwestia 7 godzin między nimi oraz powtarzający się temat twoich odpowiedzi naśladujących innych. Czy możesz to wyjaśnić?
mikeserv
1

co z używaniem ed?

ed -s test.txt <<< $'/fruits/s/apple/banana/g\nw'

(przez http://wiki.bash-hackers.org/howto/edit-ed )

andrej
źródło
edytowane, nie ma już zależności od strony internetowej
andrej
-1

Jest na to wiele sposobów i większość z nich jest naprawdę dobra, ale myślę, że ten jest moim ulubionym:

tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'

Lub nawet:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n'
mikeserv
źródło
Nie mogę w ogóle uzyskać twojej pierwszej odpowiedzi. Podziwiam elegancję drugiego, ale uważam, że trzeba go usunąć *. Tak jak jest teraz, usunie wszelkie puste linie następujące po linii kończącej się na >. … Hmm. Patrząc wstecz na pytanie, widzę, że jest to trochę niejednoznaczne. Pytanie brzmi: „Chcę usunąć wszystkie znaki nowej linii, które występują po >…”. Interpretuję to, co oznacza, że >\n\n\n\n\nfoonależy to zmienić \n\n\n\nfoo, ale przypuszczam, że foomoże to być pożądany wynik.
Scott
@Scott - Testowałem z różnymi wariantami: printf '>\n>\n\n>>\n>\n>>>\n>\nf\n\nff\n>\n' | tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'- co >>>>>>>>>>f\n\nff\n\ndla mnie daje pierwszą odpowiedź. Jestem jednak ciekawy tego, co robisz, aby to zepsuć, ponieważ chciałbym to naprawić. Co do drugiej kwestii - nie zgadzam się, że jest niejednoznaczna. PO nie prosi, aby usunąć cały > poprzedzający się \newline, ale zamiast usunąć wszystkie \n ewlines następujące> .
mikeserv
1
Tak, ale poprawna interpretacja jest taka, że >\n\n\n\n\nza pierwszym znakiem jest tylko nowa linia >; wszyscy inni podążają za nowymi liniami. Zauważ, że sugestia OP „właśnie tego chcę, jeśli tylko zadziała” sed -e 's/>\n/>/g', nie była sed -e 's/>\n*/>/g'.
Scott
1
@Scott - sugestia nie działała i nigdy nie mogła. Nie wierzę, że sugestię kodu kogoś, kto nie w pełni rozumie kod, można uznać za ważny punkt interpretacyjny jako zwykły język, którego ta osoba używa. A poza tym, wyjście - jeśli to rzeczywiście działa - od s/>\n/>/dnia >\n\n\n\n\nnadal będzie coś, co s/>\n/>/byłoby edycji.
mikeserv