Grep de la fin d'un fichier au début

39

J'ai un fichier avec environ 30.000.000 lignes (Radius Accounting) et j'ai besoin de trouver le dernier match d'un modèle donné.

La commande:

tac accounting.log | grep $pattern

donne ce dont j'ai besoin, mais c'est trop lent car le système d'exploitation doit d'abord lire tout le fichier, puis l'envoyer au canal.

J'ai donc besoin de quelque chose de rapide capable de lire le fichier de la dernière ligne à la première.

Hábner Costa
la source

Réponses:

44

tacn’aide que si vous utilisez également grep -m 1(en supposant que GNUgrep ) pour vous greparrêter après le premier match:

tac accounting.log | grep -m 1 foo

De man grep :

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

Dans l'exemple de votre question, les deux tac et le grepbesoin de traiter le fichier entier si l' aide tacest un peu vain.

Donc, sauf si vous utilisez grep -m, n'utilisez pas tacdu tout, analysez simplement le résultat de greppour obtenir le dernier match:

grep foo accounting.log | tail -n 1 

Une autre approche consisterait à utiliser Perl ou tout autre langage de script. Par exemple (où$pattern=foo ):

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

ou

awk '/foo/{k=$0}END{print k}' file
terdon
la source
1
J'utilise tac parce que je dois trouver la dernière correspondance d'un motif donné. En utilisant votre suggestion "grep -m1", le temps d’exécution passe de 0m0.597s à 0m0.007s \ o /. Merci à tous!
Hábner Costa
1
@ HábnerCosta, vous êtes le bienvenu. Je comprends pourquoi vous utilisez tac, mon problème était que cela n’aiderait pas si vous ne l’utilisiez pas également, -mcar le fichier doit encore être lu en entier par deux programmes. Sinon, vous pouvez simplement rechercher toutes les occurrences et ne conserver que la dernière comme je le fais avec tail -n 1.
terdon
6
Pourquoi dites-vous "tac [...] doit traiter tout le fichier"? La première chose à faire est de chercher à la fin du fichier et de lire un bloc à partir de la fin. Vous pouvez le vérifier vous-même avec strace (1). Combiné avec grep -m, cela devrait être assez efficace.
camh
1
@camh lorsqu'il est combiné avec grep -melle l'est. Le PO n'utilisait pas, -mdonc grep et tac traitaient le tout.
terdon
Pourriez-vous développer la signification de la awkligne?
Sopalajo de Arrierez
12

La raison pour laquelle

tac file | grep foo | head -n 1

ne s’arrête pas au premier match à cause de la mise en mémoire tampon.

Normalement, head -n 1quitte après avoir lu une ligne. Donc, grepdevrait obtenir un SIGPIPE et quitter dès qu'il écrit sa deuxième ligne.

Mais ce qui se passe, c’est que parce que sa sortie n’est pas transmise à un terminal, grep mémoire tampon. C'est-à-dire qu'il ne l'écrit pas tant qu'il n'a pas suffisamment accumulé (4096 octets dans mon test avec GNU grep).

Cela signifie que vous grepne quitterez pas avant d'avoir écrit 8192 octets de données, donc probablement pas mal de lignes.

Avec GNU grep, vous pouvez le faire quitter plus tôt en utilisant le --line-bufferedmot qui lui dit d’écrire des lignes dès qu’elles sont trouvées, qu’elles soient ou non acheminées vers un terminal. Alors grepserait alors sortir sur la deuxième ligne qu'il trouve.

Mais avec GNU de greptoute façon, vous pouvez utiliser à la -m 1place ce que @terdon a montré, ce qui est mieux car il se termine au premier match.

Si vous n’êtes greppas GNU grep, vous pouvez utiliser sedou awkremplacer. Mais tac étant une commande GNU, je doute que vous trouviez un système avec tacgrepn’est pas GNU grep.

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

Certains systèmes doivent tail -rfaire la même chose que GNU tac.

Notez que, pour les fichiers normaux (à rechercher), tacet qu'ils tail -rsont efficaces car ils lisent les fichiers à l'envers, ils ne les lisent pas entièrement en mémoire avant de les imprimer en arrière (comme le ferait l'approche @ slm ou tacdes fichiers non réguliers) .

Sur les systèmes où ni tacni tail -rsont disponibles, les seules options sont d'implémenter la lecture en arrière à la main avec des langages de programmation tels que perlou utiliser:

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

Ou:

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

Mais ceux-ci signifient trouver toutes les correspondances et n'impriment que le dernier.

Stéphane Chazelas
la source
4

Voici une solution possible qui trouvera l'emplacement de la première occurrence du motif à partir de la dernière:

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

Ceci utilise les -set dont les -rcommutateurs tacsont les suivants:

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

-r, --regex
interpret the separator as a regular expression
mkc
la source
Sauf que vous perdrez tout ce qui se situe entre le début de la ligne et le motif.
Ychaouche
2

En utilisant sed

Affichage des méthodes alternatives à l' @ bien la réponse de Terdon en utilisant sed:

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

Exemples

$ 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

Utiliser Perl

En bonus, voici une notation un peu plus facile en Perl à retenir:

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

Exemple

$ perl -e 'print reverse <>' file | grep -m 1 5
5
slm
la source
1
C'est (en particulier sedcelui) susceptible d'être de plusieurs ordres de grandeur plus lent que grep 5 | tail -n1ou sed '/5/h;$!d;g'. Il utilisera également potentiellement beaucoup de mémoire. Ce n'est pas beaucoup plus portable que vous utilisez toujours GNU grep -m.
Stéphane Chazelas