Alternativa Sed per la ricerca e la sostituzione su linee molto lunghe


9

Ho dei file che sono stati generati da un programma che non ha messo le nuove righe alla fine dei record. Voglio mettere nuove righe tra i record e posso farlo con un semplice script sed:

sed -e 's/}{/}\n{/g'

Il problema è che i file di input hanno dimensioni di più gigabyte e quindi le righe di input per sed sono lunghe più GB. sed cerca di mantenere una linea in memoria, che in questo caso non funziona. Ho provato l' --unbufferedopzione, ma questo sembrava rallentarlo e non gli ha permesso di terminare correttamente.


Sarebbe possibile caricare un file di input di esempio da qualche parte per provare alcune idee?
MK

3
Magari potresti prima usare trper tradurre }in \ne poi usare sedper aggiungere un }alla fine di ogni riga? In questo modo:tr '}' '\n' < your_file.txt| sed 's/$/}/'
user43791

L'aggiunta di una nuova riga alla fine del file aiuta affatto? Mi piace:printf "\n" >> file
tata

1
@Ketan, suppongo che scrivere un file con 78 caratteri di immondizia seguiti da }{ripetuti fino a quando non sarà sufficiente diversi gigabyte.
tata

@nanny - buon punto - ma dove prendi 78? Se i record sono già bloccati, dd if=file cbs=80 conv=unblocklo farebbe, ma raramente è così semplice.
mikeserv

Risposte:


7

È possibile utilizzare un altro strumento che consente di impostare il separatore del record di input. Per esempio

  • Perl

    perl -pe 'BEGIN{ $/="}{" } s/}{/}\n{/g' file
    

    La variabile speciale $/è il separatore del record di input. Impostandolo per }{definire le linee che finiscono in }{. In questo modo puoi ottenere ciò che vuoi senza leggere tutto in memoria.

  • mawk o gawk

    awk -v RS="}{" -vORS= 'NR > 1 {print "}\n{"}; {print}' file 
    

    Questa è la stessa idea RS="}{"imposta il separatore di record su }{e quindi si stampa }, una nuova riga {(ad eccezione del primo record) e il record corrente.


3

Perl in soccorso:

perl -i~ -e ' $/ = \1024;
              while (<>) {
                  print "\n" if $closing and /^{/;
                  undef $closing;
                  s/}{/}\n{/g;
                  print;
                  $closing = 1 if /}$/;
              } ' input1 input2

L'impostazione $/su \1024leggerà il file in blocchi di 1024 byte. La $closingvariabile gestisce il caso quando finisce un blocco }e inizia il successivo {.


1
+1, probabilmente la soluzione migliore; anche le altre soluzioni perl / awk funzionano bene, ma cosa succede se il primo separatore di record si verifica dopo circa 17 GB di caratteri?
don_crissti

2

Dovresti fare:

{ <infile tr \} \\n;echo {; } | paste -d'}\n' - /dev/null >outfile

È probabilmente la soluzione più efficiente.

Questo mette {}a proteggere tutti i possibili dati finali. Con un altro trprocesso puoi scambiarlo e fare una riga vuota all'inizio del primo {campo. Piace...

tr {} '}\n'| paste -d{\\0 /dev/null - | tr {}\\n \\n{}

Quindi il primo, con i dati di esempio di don, fa:

printf '{one}{two}{three}{four}' |
{ tr \} \\n; echo {; }           |
paste -d'}\n' - /dev/null
{one}
{two}
{three}
{four}
{}

... e il secondo lo fa ...

printf '{one}{two}{three}{four}'      |
tr {} '}\n'| paste -d{\\0 /dev/null - |
tr {}\\n \\n{}
#leading blank
{one}
{two}
{three}
{four}

Non esiste una nuova riga finale per il secondo esempio, sebbene ce ne sia una per il primo.


0

Un'utilità di sedtipo binario chiamatabbe

Trovo più facile rimanere con una sintassi simile a sed in questo caso.

Ho molto preferisco usare il bbeprogramma di utilità (disponibile tramite il vostro {uni, Linu} l'installazione dei pacchetti di x, eq apt-get). O qui se sei una delle persone git, anche se non ho verificato personalmente quel particolare link.

1. Supporta il s/before/after/linguaggio

È un "Binary Block Editor", che supporta le operazioni sed-like (tra le altre). Ciò include il s/before/after/linguaggio di sostituzione super comune di cui hai bisogno. Nota, poiché dal bbepunto di vista non ci sono linee di per sé , non c'è "g globale" alla fine del comando.

Come test rapido (notare quanto richiesto -e):

$ echo hello | bbe -e 's/l/(replaced)/'

produce:

he(replaced)(replaced)o

2. Nel caso specifico di }{per }\n{conversione

Quindi se avessimo un file enorme riempito con un milione di numeri nel (diciamo) formato {1}{2}{3}... {1000000}senza ritorni a capo, potremmo scambiarli facilmente }{con }\n{, e avere tutti i numeri uno per riga.

Questo sarebbe con questo bbecomando:

bbe -e 's/}{/}\n{/'

Come testato in questo loop zsh, che prendiamo solo la coda di:

$ for ((num=0; num<1000000; num++)) do; echo -n "{$num}"; done | bbe -e 's/}{/}\n{/' | tail

Che produrrebbe questo:

{999990}
{999991}
{999992}
{999993}
{999994}
{999995}
{999996}
{999997}
{999998}
{999999}

(senza un ritorno in carrozza finale ovviamente.)

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.