52

I'm parsing a mailbox file that stores e-mail server reports for unsuccessfully delivered e-mail. I wish to extract bad e-mail addresses, so that I remove them from the system. The log file looks like this:

...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.

E-mail address comes 2 lines after a line with "The mail system". Using grep like this gives me the "The mail system" line and the next two lines:

grep -A 2 "The mail system" mbox_file

However, I don't know how to remove the "The mail system" line and the second empty line from this output. I guess I could write PHP/Perl/Python script to do it, but I wonder if this is possible with grep or some other standard tool. I tried to give negative offset to -B parameter:

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

But grep complains:

grep: -2: invalid context length argument

Is there a way to do this with grep?

5
  • 3
    -B accepts numeral as -A would, and it would display the previous lines before the match. Commented Feb 13, 2012 at 12:21
  • 3
    Yes, that is true, but Milan isn't interested in what precedes the match... The problem he encountered is that -A and -B only accept positive values... and that in any case, -A and -B can't be used relative to each other, as he has attempted to do.
    – Peter.O
    Commented Feb 13, 2012 at 14:17
  • 1
    Hum, just to make sure: those are dummy addresses that you did not (directly) extract from the file you were given, right ? Commented Feb 13, 2012 at 15:20
  • 1
    @Matthieu M. no, they are from real log file. I figured since they are invalid addresses anyway, what's the point of inventing dummy addresses that might be valid. Commented Feb 13, 2012 at 19:52

6 Answers 6

40

The simplest way to solve it using grep only, is to pipe one more inverted grep at the end. For example:

grep -A 4 "The mail system" temp.txt | grep -v "The mail system" | grep -v '^\d*$'
0
32

If you aren't locked in to using grep, try sed ...

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

When it finds a line containing "The mail system", it reads the next line twice, via the n;n;, discarding each previous line as it does so.
This leaves the 3rd line of your group in the pattern space, which is then printed via sed's p command.. The leading -n option prevents all other printing.

To print the next two lines as well, it is just a case of next and print n;p twice more.

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

The next-line reads for the lines you require can be accumulated and printed a a single block with just one p... N reads the next line and appends it to the pattern space,

Here is the final condensed version...

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

If you want a group seperator, similar to what grep wouuld output, you can use sed's insert command i (which must be the last command on a line)...

Here is the syntax to include a group seperator

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

Here is the output for the first match:

<[email protected]>: host mx1.hotmail.com[65.54.188.94] said: 550
    Requested action not taken: mailbox unavailable (in reply to RCPT TO
    command)                                                                    
--
2
  • +1. Thanks. I don't need it in this case, but I'll keep this bookmarked in case I get more complicated stuff to handle. Commented Feb 14, 2012 at 19:42
  • This is a great answer!
    – dotancohen
    Commented Jan 24, 2013 at 12:45
15
grep -A 2 -B -2 "The mail system" mbox_file

-B is for previous lines, so no need to give -negative value.

grep -A 2 -B 2 "The mail system" mbox_file   # This will work please check
1
  • This does not answer the question. -A 2 -B 2 prints from two lines before the context to 2 lines after the context. The question is about printing from 2 lines after the context to 4 lines after the context. Commented Feb 12, 2018 at 14:56
3

I see no point in using only grep(s), except if that's a strict constraint. It cannot be done with one call to grep.

grep -A 2 "The mail system" mbox_file | tail -n +3
  • grep: Find the line and output 2 lines after,
  • tail: cut the first 2 lines (i.e. start from the third line).
3
  • 2
    This only works if there is a single matching line, which is probably not what the question is asking.
    – jw013
    Commented Nov 8, 2012 at 17:03
  • That is nothing what the question asked for but it helps me in my current situation :-) . Commented Feb 12, 2018 at 14:57
  • 1
    @daniel.neumann I know, but I was exactly in your shoes and thought others' Google-fu will lead here too.
    – TWiStErRob
    Commented Feb 12, 2018 at 15:24
1

If yo want to remove the first 2 lines pipe it to sed

sed '1,2d'

as in

grep -A 2 "The mail system" mbox_file | sed '1,2d'
1
  • You’ve missed the fact that the pattern occurs many times.   If “The mail system” appears on lines 4, 14, 24, 34, …, the OP wants to see lines 6, 16, 26, 36, …   Your answer will give 6, 14-16, 24-26, 34-36, …. Commented Aug 21, 2020 at 5:41
0

This prints the next 1 line following the regexp match, using Perl

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .