Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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

<slavicatomic118@hotmail.com>: 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

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

...some content...
                   The mail system

<sigirna_luka@yahoo.com>: host mta5.am0.yahoodns.net[74.6.140.64] said: 554
    delivery error: dd This user doesn't have a yahoo.com account
    (sigirna_luka@yahoo.com) [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?

share|improve this question
1  
-B accepts numeral as -A would, and it would display the previous lines before the match. –  Nikhil Mulley Feb 13 '12 at 12:21
1  
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 Feb 13 '12 at 14:17
    
Hum, just to make sure: those are dummy addresses that you did not (directly) extract from the file you were given, right ? –  Matthieu M. Feb 13 '12 at 15:20
    
@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. –  Milan Babuškov Feb 13 '12 at 19:52
add comment

5 Answers

up vote 7 down vote accepted

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*$'
share|improve this answer
add comment

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:

<slavicatomic118@hotmail.com>: host mx1.hotmail.com[65.54.188.94] said: 550
    Requested action not taken: mailbox unavailable (in reply to RCPT TO
    command)                                                                    
--
share|improve this answer
    
+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. –  Milan Babuškov Feb 14 '12 at 19:42
    
This is a great answer! –  dotancohen Jan 24 '13 at 12:45
add comment
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
share|improve this answer
add comment

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).
share|improve this answer
1  
This only works if there is a single matching line, which is probably not what the question is asking. –  jw013 Nov 8 '12 at 17:03
add comment

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

perl -ne 'print if( (/The mail system/ && ($end=1))..!$end-- )' 
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.