grep ignore n lignes de fichier et ne recherche que

9

J'ai un énorme fichier journal et je veux grep la première occurrence d'un modèle, puis trouver un autre modèle juste après cette occurrence.

Par exemple:

123
XXY
214
ABC
182
558
ABC
856
ABC

Dans mon exemple, je voudrais trouver 182puis trouver la prochaine occurrence deABC

La première occurrence est simple:

grep -n -m1 "182" /var/log/file

Cela produit:

5:182

Comment trouver la prochaine occurrence d'ABC?

Mon idée était de dire grepde sauter les premières nlignes (dans l'exemple ci-dessus n=5), sur la base du numéro de ligne de 182. Mais comment faire?

koljanep
la source
1
Est-ce une exigence qui grepest utilisée? Je ne pense pas que cela puisse être fait avec grepmais ce serait facile avec awkou sed(seul ou en combinaison avec grep).
Hauke ​​Laging du
@HaukeLaging grepn'est pas requis. Je ne suis pas encore familier avec sedou awk. Si vous avez une bonne solution, laissez-moi l'entendre! :) @don_crissti seule la première ligne doit être imprimée. Je me fiche des autres événements.
koljanep

Réponses:

10

Avec, sedvous pouvez utiliser une plage et une qentrée en une seule fois:

sed '/^182$/p;//,/^ABC$/!d;/^ABC$/!d;q'

De même avec GNU, grepvous pouvez diviser l'entrée entre deux greps:

{ grep -nxF -m1 182; grep -nxF -m1 ABC; } <<\IN
123
XXY
214
ABC
182
558
ABC
856
ABC
IN

... qui imprime ...

5:182
2:ABC

... pour signifier que le premier a greptrouvé un -Flittéral de chaîne ixe, une -xligne entière 182 correspond à 5 lignes depuis le début de sa lecture, et le second a trouvé un ABC de type similaire correspondant à 2 lignes depuis le début de sa lecture - ou 2 lignes après le premier grep arrêt de lecture à la ligne 5.

De man grep:

-m NUM, --max-count=NUM
          Stop  reading  a  file  after  NUM  matching
          lines.   If the input is standard input from
          a regular file, and NUM matching  lines  are
          output, grep ensures that the standard input
          is  positioned  to  just  after   the   last
          matching  line before exiting, regardless of
          the  presence  of  trailing  context  lines.
          This  enables  a calling process to resume a
          search. 

J'ai utilisé un document ici pour une démonstration reproductible, mais vous devriez probablement le faire:

{ grep ...; grep ...; } </path/to/log.file

Il fonctionnera également avec d'autres constructions de commandes composées de shell comme:

for p in 182 ABC; do grep -nxFm1 "$p"; done </path/to/log.file
mikeserv
la source
+1 J'ai vu cela dans la page de manuel. C'est ce que j'ai essayé, seulement avec une pipe entre les grep's au lieu d'un ;... no-go
Xen2050
@ Xen2050 - le canal ne fonctionnera pas, généralement - un fichier lseekable est généralement ce que vous voulez lorsque vous partagez une entrée.
mikeserv
Réponse impressionnante mais je ne soutiens pas votre déclaration sur les pipelines. Le document que les deux greppartagent ici est effectivement un pipeline pour eux. Autre chose: j'ai essayé sans imprimer la ligne de marqueur mais sed '//,/^ABC$/!d;/^ABC$/!d;q'jette une étrange erreur. Que fait //-il?
Hauke ​​Laging
1
@HaukeLaging - le document ici (dans la plupart des shells) n'est pas un tube - c'est un vrai fichier tmp créé par le shell que le shell supprime avant de faire des écritures - tout en conservant le descripteur. C'est encore lseekable. Les tuyaux, en général, ne peuvent pas être recherchés. Je vais regarder la sedchose - je l'ai juste écrite très rapidement.
mikeserv
1
@HaukeLaging - Oh, donc la sedchose fonctionne - vous venez de laisser de côté la référence. Dans sedvous pouvez vous référer à /address/nouveau au dernier avec une //adresse vide . Il en va de /^182$/command;//,/next_address/même /^182$/command;/^182$/,/next_address/. Votre erreur n'était probablement pas une expression régulière précédente si vous utilisiez un GNU sed. Soit dit en passant, le tuyau lseek peut être manipulé par indirection via les /dev/fd/[num]liens sur les systèmes Linux - mais si vous ne faites pas très attention à bien gérer les tampons (comme avec dd), c'est généralement une bataille perdue.
mikeserv
2

Utiliser grepavec des expressions régulières compatibles Perl ( pcregrep):

pcregrep -Mo '182(.|\n)*?\KABC'

L'option -Mpermet au modèle de correspondre à plusieurs lignes et \Kn'inclut pas le modèle correspondant (jusqu'à ce point) dans la sortie. Vous pouvez supprimer \Ksi vous souhaitez avoir la région entière en conséquence.

jimmij
la source
2
> awk '/^182$/ { startline=1; }; startline == 0 { next; }; /^ABC$/ { print "line " NR ": " $0; exit; }' file
line 7: ABC
Hauke ​​Laging
la source
1
Cela donne le premier ABC n'importe où ; cette question veut le premier ABC après les 18 premiers. Le plus direct est un drapeau comme awk '/^182$/{z=1;next} z&&/^ABC$/{print NR":"$0;exit}' file- ou vous pouvez écrire au moins une getline()boucle explicite qui est généralement plus maladroite, ou être intelligent (?) en utilisant une plage presque similaire à @ JRFerguson perl:awk '!x&&/^182$/,/^ABC$/ {x=NR":"$0} END{print x}
dave_thompson_085
@ dave_thompson_085 En effet. Bonne idée mais terriblement codée (mélangé deux idées lors de l'écriture). Malheureusement, j'ai même essayé, mais je ne me suis pas étonné de la sortie.
Hauke ​​Laging du
1

Une variante Perl que vous pourriez utiliser est:

perl -nle 'm/182/../ABC/ and print' file

... qui imprime des lignes dans la plage correspondante.

Si votre fichier contient plusieurs plages correspondantes, vous pouvez limiter la sortie à la première plage uniquement en remplaçant le /délimiteur par?

perl -nle 'm?182?..?ABC? and print'
JRFerguson
la source
1

Rester avec juste grepet ajouter tail& cut, vous pourriez ...

grep pour le numéro de ligne de la première correspondance de 182:

grep -m 1 -n 182 /var/log/file |cut -f1 -d:

Utilisez cela pour grep pour tous les ABC's seulement après la première ligne correspondante ci-dessus, en utilisant tail' s -n +Kpour sortir après la Kème ligne. Tous ensemble:

tail -n +$(grep -m 1 -n 182 /var/log/file |cut -f1 -d:) /var/log/file | grep ABC

Ou ajoutez à -m 1nouveau pour ne trouver que la première correspondanceABC

tail -n +$(grep -m 1 -n 182 /var/log/file|cut -f1 -d:) /var/log/file|grep -m 1 ABC

Références:
manpages
/programming/6958841/use-grep-to-report-back-only-line-numbers

Xen2050
la source
1

Une autre variante est la suivante:

grep -n -A99999 "182" /var/log/file|grep -n -m1 "ABC"

Le drapeau -An salue n lignes après le match et 99999 est juste pour être sûr que nous ne manquons de rien. Les fichiers plus gros devraient avoir plus de lignes (vérifiez avec "wc -l").

Fabbe
la source
0

L'opérateur de plage ,peut être utilisé ici:

< yourfile \
sed -e '
   /182/,/ABC/!d
   //!d;=;/ABC/q
' | sed -e 'N;s/\n/:/'

L'opérateur de plage ..en tandem avec l' opérateur de correspondance unique m??peut être utilisé ici dansPerl

perl -lne 'm?182? .. m?ABC? and print "$.:$_" if /182/ || /ABC/' yourfile

la source