Sostituisci la stringa contenente newline in un file enorme


16

Qualcuno sa di uno strumento non basato su linea per cercare "binario" / sostituire le stringhe in modo un po 'efficiente in termini di memoria? Vedi anche questa domanda .

Ho un file di testo + 2 GB che vorrei elaborare in modo simile a quello che sembra fare:

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

Ciò significa che voglio rimuovere tutte le nuove righe che si verificano dopo un >, ma non altrove, in modo da escludere tr -d.

Questo comando (che ho ricevuto dalla risposta di una domanda simile ) fallisce con couldn't re-allocate memory:

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

Quindi, ci sono altri metodi senza ricorrere a C? Odio il Perl, ma sono disposto a fare un'eccezione in questo caso :-)

Non sono sicuro di alcun personaggio che non si presenta nei dati, quindi la sostituzione temporanea \ncon un altro personaggio è qualcosa che vorrei evitare, se possibile.

Qualche buona idea, qualcuno?


Hai provato l'opzione --unbuffered?
ctrl-alt-delor,

Con o senza --unbufferedmemoria
esaurita

Cosa fa $!?
ctrl-alt-delor,

Cosa c'è di sbagliato nel primo comando sed. Il secondo sembra leggere tutto nello spazio del modello, non so che lo $!sia. Ciò che mi aspetto avrà bisogno di MOLTA memoria.
ctrl-alt-delor,

Il problema è che sed legge tutto come righe, ecco perché il primo comando non rimuove le nuove righe, poiché restituisce il testo riga per riga. Il secondo comando è solo una soluzione alternativa. Penso che sednon sia lo strumento giusto in questo caso.
MattBianco,

Risposte:


14

Questo è davvero banale in Perl, non dovresti odiarlo!

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

Spiegazione

  • -i: modifica il file sul posto e crea un backup dell'originale chiamato file.bak. Se non si desidera un backup, utilizzare perl -i -peinvece.
  • -pe: leggi il file di input riga per riga e stampa ogni riga dopo aver applicato lo script indicato come -e.
  • s/>\n/>/: la sostituzione, proprio come sed.

Ed ecco un awkapproccio:

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

3
+1. awk golf:awk '{ORS=/>$/?"":"\n"}1'
glenn jackman,

1
Perché non mi piace perl in generale è lo stesso motivo per cui ho scelto questa risposta (o in realtà il tuo commento alla risposta di Gnouc): leggibilità. L'uso di perl -pe con un semplice "modello sed" è molto più leggibile di una complessa espressione sed.
MattBianco,

3
@MattBianco è abbastanza giusto, ma, per così dire, non ha nulla a che fare con Perl. L'aspetto che Gnouc ha usato è una caratteristica di alcuni linguaggi di espressione regolare (inclusi, ma non limitati a, PCRE), non è affatto colpa di Perl. Inoltre, dopo aver inserito questa mostruosità sed ':a;N;$!ba;s/>\n/>/g'nella tua domanda, hai rinunciato al tuo diritto di lamentarti della leggibilità! : P
terdon

@glennjackman nice! Stavo giocando con il foo ? bar : bazcostrutto ma non riuscivo a farlo funzionare.
terdon

@terdon: sì, il mio errore. Cancellalo.
cuonglm,

7

UN perl soluzione:

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

spiegazione

  • s/// è usato per la sostituzione di stringhe.
  • (?<=>) è il modello lookbehind.
  • \n corrisponde a newline.

L'intero significato dei pattern rimuove tutte le newline che hanno >davanti.


2
ti interessa commentare cosa fanno le parti del programma? Cerco sempre di imparare.
MattBianco,

2
Perché preoccuparsi del lookbehind? Perché non solo s/>\n/>/?
terdon

1
o s/>\K\n//funzionerebbe anche
Glenn Jackman,

@terdon: solo la prima cosa che ho rimosso, invece di sostituirlo
cuonglm

@glennjackman: buon punto!
cuonglm,

3

Cosa ne pensi di questo:

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

Per GNU sed, puoi anche provare ad aggiungere l' opzione -u( --unbuffered) come da domanda. GNU sed è anche contento di questo come un semplice one-liner:

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

Ciò non rimuove l'ultimo \nse il file finisce >\n, ma probabilmente è preferibile comunque.
Stéphane Chazelas,

@ StéphaneChazelas, perché la chiusura }deve essere in un'espressione separata? questo non funzionerà come espressione multilinea?
Graeme,

1
Funzionerà con POSIX seds con b loop\n}o -e 'b loop' -e '}'ma non come b loop;}e certamente non come b loop}perché }e ;sono validi nei nomi delle etichette (anche se nessuno nella loro mente corretta lo userebbe. Ciò significa che GNU sed non è conforme a POSIX) e il }comando deve essere separato dal bcomando.
Stéphane Chazelas,

@ StéphaneChazelas, GNU sedè contento di tutto quanto sopra anche con --posix! Lo standard ha anche quanto segue per le espressioni di parentesi graffe - The list of sed functions shall be surrounded by braces and separated by <newline>s. Questo non significa che i punti e virgola dovrebbero essere usati solo al di fuori delle parentesi graffe?
Graeme,

@mikeserv, il ciclo è necessario per gestire le righe consecutive che terminano con >. L'originale non ne ha mai avuto uno, questo è stato sottolineato da Stéphane.
Graeme,

1

Dovresti essere in grado di usarlo sedcon il Ncomando, ma il trucco sarà quello di eliminare una riga dallo spazio del pattern ogni volta che ne aggiungi un'altra (in modo che lo spazio del pattern contenga sempre solo 2 righe consecutive, invece di provare a leggere l'intero file) - prova

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

EDIT: dopo aver riletto il famoso Sed One-Liners di Peteris Krumins, ho spiegato che credo che una sedsoluzione migliore sarebbe

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

che aggiunge solo la seguente riga nel caso in cui sia già stata creata una >corrispondenza alla fine e dovrebbe ricorrere in modo condizionale indietro per gestire il caso di linee di corrispondenza consecutive (è il 39 di Krumin . Aggiungi una riga alla successiva se termina con una barra rovesciata) "\" esattamente ad eccezione della sostituzione di >per \come carattere join, e il fatto che il join carattere viene mantenuto nell'output).


2
Questo non funziona se 2 linee consecutive finiscono >(che è anche GNU specifico)
Stéphane Chazelas,

1

sednon fornisce un modo per emettere output senza una nuova riga finale. Il tuo approccio usandoN fondamentalmente funziona, ma memorizza le linee incomplete nella memoria, e quindi può fallire se le linee diventano troppo lunghe (le impianti sed non sono in genere progettate per gestire linee estremamente lunghe).

Puoi usare invece awk.

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

Un approccio alternativo consiste nell'utilizzare trper scambiare il carattere newline con un carattere “noioso”, che si presenta frequentemente. Lo spazio potrebbe funzionare qui: scegli un personaggio che tende ad apparire su ogni riga o almeno in una grande proporzione di righe nei tuoi dati.

tr ' \n' '\n ' | sed 's/> />/g' | tr '\n ' ' \n'

Entrambi i metodi sono già stati dimostrati qui per migliorare l'effetto in altre risposte. E il suo approccio con sednon funziona senza un buffer da 2,5 GB.
Mikeserv,

Qualcuno ha menzionato Awk? Oh, mi mancava, avevo notato perl nella risposta di Terdon per qualche motivo. Nessuno ha menzionato l' trapproccio - mikeserv, hai pubblicato un approccio diverso (valido, ma meno generico) che sembra usare tr.
Gilles 'SO- smetti di essere malvagio' il

suoni validi, ma meno generici per me come hai appena chiamato una soluzione funzionante e mirata. penso che sia difficile sostenere che una cosa del genere non sia utile, il che è strano perché ha 0 voti. La più grande differenza che posso vedere tra la mia soluzione e la tua offerta più generica , è che la mia risolve specificamente un problema, mentre la tua potrebbe generalmente. Ciò potrebbe rendere utile - e potrei anche invertire il mio voto - ma c'è anche la fastidiosa questione delle 7 ore tra loro e il tema ricorrente delle tue risposte che imita gli altri. Puoi spiegarlo?
Mikeserv,



-1

Ci sono molti modi per farlo, e la maggior parte qui è davvero buona, ma penso che questo sia il mio preferito:

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

O anche:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n'

Non riesco affatto a far funzionare la tua prima risposta. Mentre ammiro l'eleganza del secondo, credo che sia necessario rimuovere il *. Così com'è ora, eliminerà tutte le righe vuote che seguono una riga che termina con a >. ... Hmm. Guardando indietro alla domanda, vedo che è un po 'ambiguo. La domanda dice: "Voglio rimuovere tutte le nuove righe che si verificano dopo un >, ..." Interpreto ciò per dire che >\n\n\n\n\nfoodovrebbe essere modificato in \n\n\n\nfoo, ma suppongo che foopotrebbe essere l'output desiderato.
Scott,

@Scott - Ho provato con variazioni su quanto segue: printf '>\n>\n\n>>\n>\n>>>\n>\nf\n\nff\n>\n' | tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'- che risulta >>>>>>>>>>f\n\nff\n\nper me con la prima risposta. Sono curioso di sapere cosa stai facendo per romperlo, perché mi piacerebbe risolverlo. Quanto al secondo punto: non concordo sul fatto che sia ambiguo. Il PO non chiede di rimuovere tutti > precede un \newline, ma invece di rimuovere tutte \n ewlines seguenti una >.
Mikeserv,

1
Sì, ma un'interpretazione valida è che, in >\n\n\n\n\n, solo la prima riga successiva è dopo a >; tutti gli altri stanno seguendo altre nuove linee. Si noti che il suggerimento del PO "questo è quello che voglio, se solo funzionasse" sed -e 's/>\n/>/g'non lo era sed -e 's/>\n*/>/g'.
Scott,

1
@Scott: il suggerimento non ha funzionato e non è mai stato possibile. Non credo che il suggerimento del codice di qualcuno che non comprende appieno il codice possa essere considerato valido come punto di interpretazione come il linguaggio semplice usato da quella persona. E inoltre, l'output - se funzionasse davvero - di s/>\n/>/on >\n\n\n\n\nsarebbe comunque qualcosa da s/>\n/>/modificare.
Mikeserv,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.