Comment afficher les lignes 2 à 4 après chaque résultat grep?

39

J'analyse un fichier de boîte aux lettres qui stocke les rapports du serveur de messagerie pour les courriers électroniques remis en échec. Je souhaite extraire les mauvaises adresses électroniques afin de les supprimer du système. Le fichier journal ressemble à ceci:

...some content...
                   The mail system

<[email protected]>: host mx1.hotmail.com[65.54.188.94] said: 550
    Requested action not taken: mailbox unavailable (in reply to RCPT TO
    command)

...some content...
                   The mail system

<[email protected]>: host viking.optimumpro.net[79.101.51.82] said: 550
    Unknown user (in reply to RCPT TO command)

...some content...
                   The mail system

<[email protected]>: host mta5.am0.yahoodns.net[74.6.140.64] said: 554
    delivery error: dd This user doesn't have a yahoo.com account
    ([email protected]) [0] - mta1172.mail.sk1.yahoo.com (in reply to end
    of DATA command)

...etc.

L'adresse e-mail vient 2 lignes après une ligne avec "Le système de messagerie". Utiliser grep comme ceci me donne la ligne "Le système de messagerie" et les deux lignes suivantes:

grep -A 2 "The mail system" mbox_file

Cependant, je ne sais pas comment supprimer la ligne "Le système de messagerie" et la deuxième ligne vide de cette sortie. Je suppose que je pourrais écrire un script PHP / Perl / Python pour le faire, mais je me demande si cela est possible avec grep ou un autre outil standard. J'ai essayé de donner un décalage négatif au paramètre -B:

grep -A 2 -B -2 "The mail system" mbox_file

Mais grep se plaint:

grep: -2: invalid context length argument

Existe-t-il un moyen de faire cela avec grep?

Milan Babuškov
la source
3
-B accepte les chiffres comme -A et afficherait les lignes précédentes avant la correspondance.
Nikhil Mulley
3
Oui, c'est vrai, mais Milan ne s'intéresse pas à ce qui précède le match ... Le problème qu'il a rencontré est que -A et -B n'acceptent que les valeurs positives ... et que dans tous les cas, -A et -B peuvent ne pas être utilisé les uns par rapport aux autres, comme il a tenté de le faire.
Peter.O
1
Hum, juste pour être sûr: ce sont des adresses factices que vous n'avez pas (directement) extraites du fichier que vous avez reçu, non?
Matthieu M.
1
@Matthieu M. non, ils proviennent d'un fichier journal réel. Comme il s’agit de toute façon d’adresses non valides, j’ai pensé qu’il était intéressant d’inventer des adresses factices qui pourraient être valides.
Milan Babuškov
stackoverflow.com/questions/8101701/…
Ciro Santilli a annoncé le

Réponses:

29

Le moyen le plus simple de le résoudre en utilisant grepuniquement le procédé consiste à diriger un autre canal inversé grepà la fin. Par exemple:

grep -A 4 "The mail system" temp.txt | grep -v "The mail system" | grep -v '^\d*$'
Eugene S
la source
28

Si vous n'êtes pas obligé d'utiliser grep, essayez sed...

sed -n '/The mail system/{n;n;p}' 

Lorsqu'il trouve une ligne contenant "Le système de messagerie", il lit la ligne suivante deux fois, via le n;n;, en supprimant chaque ligne précédente.
Cela laisse la 3ème ligne de votre groupe dans l'espace du modèle, qui est ensuite imprimé via la pcommande de sed . L' -noption en tête empêche toute autre impression.

Pour imprimer également les deux lignes suivantes, il suffit de passer à la suivante et d’imprimer n;p deux fois plus.

sed -n '/The mail system/{n; n;p; n;p; n;p}'   

Les lectures de ligne suivante pour les lignes dont vous avez besoin peuvent être cumulées et imprimées en un seul bloc avec un seul p... Nlit la ligne suivante et l'ajoute à l'espace du motif,

Voici la version condensée finale ...

sed -n '/The mail system/{n;n;N;N;p}'   

Si vous voulez un séparateur de groupe , similaire à ce que grep wouuld produira, vous pouvez utiliser la commande insert de sed i(qui doit être la dernière commande sur une ligne) ...

Voici la syntaxe pour inclure un séparateur de groupe

sed -n '/The mail system/{n;n;N;N;p;i--
       }' > output-file  # or | ...

Voici le résultat de la première correspondance:

<[email protected]>: host mx1.hotmail.com[65.54.188.94] said: 550
    Requested action not taken: mailbox unavailable (in reply to RCPT TO
    command)                                                                    
--
Peter.O
la source
+1 Merci. Je n'en ai pas besoin dans ce cas, mais je garderai ce repère dans le cas où j'aurais des choses plus compliquées à gérer.
Milan Babuškov
C'est une excellente réponse!
dotancohen
9
grep -A 2 -B -2 "The mail system" mbox_file

-B est pour les lignes précédentes, donc pas besoin de donner une valeur négative.

grep -A 2 -B 2 "The mail system" mbox_file   # This will work please check
Mukesh Payghan
la source
Cela ne répond pas à la question. -A 2 -B 2imprime de deux lignes avant le contexte à deux lignes après le contexte. La question concerne l'impression de 2 lignes après le contexte à 4 lignes après le contexte.
daniel.neumann
1

Je ne vois aucun intérêt à utiliser uniquement grep (s), sauf s'il s'agit d'une contrainte stricte. Cela ne peut pas être fait avec un seul appel à grep.

grep -A 2 "The mail system" mbox_file | tail -n +3
  • grep: trouve la ligne et affiche 2 lignes après,
  • tail: coupe les 2 premières lignes (c.-à-d. à partir de la troisième ligne).
TWiStErRob
la source
2
Cela ne fonctionne que s'il y a une seule ligne correspondante, ce qui n'est probablement pas ce que la question demande.
jw013
Ce n'est pas ce que la question demandait, mais cela m'aide dans ma situation actuelle :-).
daniel.neumann
1
@ daniel.neumann Je sais, mais j'étais exactement à votre place et je pensais que le Google-Fu des autres mènerait ici aussi.
TWiStErRob
0

Ceci imprime la 1 ligne suivante après le match regexp, en utilisant Perl

perl -ne 'print if( (/The mail system/ && ($end=1))..!$end-- )' 
Noelbk
la source