Grep dalla fine di un file all'inizio


39

Ho un file con circa 30.000.000 di righe (Radius Accounting) e ho bisogno di trovare l'ultima corrispondenza di un determinato modello.

Il comando:

tac accounting.log | grep $pattern

dà ciò di cui ho bisogno, ma è troppo lento perché il sistema operativo deve prima leggere l'intero file e quindi inviarlo alla pipe.

Quindi, ho bisogno di qualcosa di veloce in grado di leggere il file dall'ultima riga alla prima.

Risposte:


44

tacaiuta solo se usi grep -m 1(supponendo GNU grep) anche per grepfermarti dopo la prima partita:

tac accounting.log | grep -m 1 foo

Da man grep:

   -m NUM, --max-count=NUM
          Stop reading a file after NUM matching lines.  

Nell'esempio della tua domanda, entrambi tace grepdevono elaborare l'intero file, quindi l'utilizzo tacè in qualche modo inutile.

Quindi, a meno che tu non lo usi grep -m, non usare tacaffatto, basta analizzare l'output di grepper ottenere l'ultima corrispondenza:

grep foo accounting.log | tail -n 1 

Un altro approccio sarebbe quello di usare il Perl o qualsiasi altro linguaggio di scripting. Ad esempio (dove $pattern=foo):

perl -ne '$l=$_ if /foo/; END{print $l}' file

o

awk '/foo/{k=$0}END{print k}' file

1
Sto usando tac perché devo trovare l'ultima corrispondenza di un determinato modello. Usando il tuo suggerimento "grep -m1" il tempo di esecuzione va da 0m0.597s a 0m0.007s \ o /. Grazie a tutti!
Hábner Costa,

1
@ HábnerCosta sei il benvenuto. Capisco perché stai usando tac, il mio punto era che non aiuta se non lo usi anche -mperché il file deve ancora essere letto per intero da due programmi. Altrimenti, potresti semplicemente cercare tutte le occorrenze e conservare solo l'ultima come faccio io tail -n 1.
terdon

6
Perché dici "tac [...] deve elaborare l'intero file"? La prima cosa che fa tac è cercare la fine del file e leggere un blocco dalla fine. Puoi verificarlo tu stesso con strace (1). Se combinato con grep -m, dovrebbe essere abbastanza efficiente.
Camh

1
@camh quando combinato con grep -messo è. L'OP non stava usando, -mquindi sia grep che tac stavano elaborando il tutto.
terdon

Potresti per favore espandere il significato della awklinea?
Sopalajo de Arrierez,

12

La ragione per cui

tac file | grep foo | head -n 1

non si ferma alla prima partita è a causa del buffering.

Normalmente, head -n 1esce dopo aver letto una riga. Quindi grepdovrebbe ottenere un SIGPIPE ed uscire non appena scrive la sua seconda riga.

Ma ciò che accade è che, poiché il suo output non sta andando a un terminale, lo grepbufferizza. Cioè, non lo sta scrivendo fino a quando non si è accumulato abbastanza (4096 byte nel mio test con GNU grep).

Ciò significa che grepnon uscirà prima che abbia scritto 8192 byte di dati, quindi probabilmente un bel po 'di righe.

Con GNU grep, puoi farlo uscire prima usando ciò --line-bufferedche gli dice di scrivere le linee non appena vengono trovate, indipendentemente dal fatto che vada a un terminale o meno. Quindi grepuscirebbe sulla seconda linea che trova.

Ma con GNU greppuoi comunque usare -m 1come ha mostrato @terdon, il che è meglio quando esce alla prima partita.

Se la tua grepnon è la GNU grep, puoi usare sedo awkinvece. Ma tac essendo un comando GNU, dubito che troverai un sistema con tacdove grepnon è GNU grep.

tac file | sed "/$pattern/!d;q"                             # BRE
tac file | P=$pattern awk '$0 ~ ENVIRON["P"] {print; exit}' # ERE

Alcuni sistemi devono tail -rfare la stessa cosa di GNU tac.

Si noti che, per i file regolari (ricercabili) tace tail -rsono efficienti perché leggono i file all'indietro, non stanno solo leggendo il file completamente in memoria prima di stamparlo all'indietro (come farebbe l' approccio di @ slm sed o tacsu file non regolari) .

Su sistemi in cui tacné nessuno dei due tail -rè disponibile, le uniche opzioni sono implementare manualmente la lettura all'indietro con linguaggi di programmazione come perlo utilizzare:

grep -e "$pattern" file | tail -n1

O:

sed "/$pattern/h;$!d;g" file

Ma quelli significano trovare tutte le partite e stampare solo l'ultima.


4

Ecco una possibile soluzione che troverà la posizione della prima occorrenza del modello dall'ultima:

tac -s "$pattern" -r accounting.log | head -n 1

Questo utilizza i pulsanti -se che sono i seguenti:-rtac

-s, --separator=STRING
use STRING as the separator instead of newline

-r, --regex
interpret the separator as a regular expression

Tranne che perderete tutto ciò che è tra l'inizio della linea e il modello.
ychaouche,

2

Usando sed

Mostrando alcuni metodi alternativi alla risposta eccellente di @ Terdon usando sed:

$ sed '1!G;h;$!d' file | grep -m 1 $pattern
$ sed -n '1!G;h;$p' file | grep -m 1 $pattern

Esempi

$ seq 10 > file

$ sed '1!G;h;$!d' file | grep -m 1 5
5

$ sed -n '1!G;h;$p' file | grep -m 1 5
5

Utilizzando Perl

Come bonus ecco una notazione un po 'più semplice da ricordare in Perl:

$ perl -e 'print reverse <>' file | grep -m 1 $pattern

Esempio

$ perl -e 'print reverse <>' file | grep -m 1 5
5

1
Questo è (specialmente sedquello) probabilmente più lento di grep 5 | tail -n1o di diversi ordini di grandezza sed '/5/h;$!d;g'. Potrà inoltre utilizzare molta memoria. Non è molto più portatile perché stai ancora usando GNU grep -m.
Stéphane Chazelas,
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.