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.

With grep, I want to select all lines that match a pattern, and that don't match another pattern. I want to be able to use a single invocation of grep so that I can use the --after-context option (or --before-context, or --context).

-v is not viable here, as it negates all patterns I pass to grep using the -e option.

Example

I want to look for lines matching needle, ignoring lines matching ignore me, with one line of following context.

Here's my input file:

one needle ignore me
two
three
four needle
five

The output I want is:

four needle
five

As you can see, this naive solution doesn't work:

$ cat file | grep --after-context=1 needle | grep -v 'ignore me'
two
---
four needle
five
share|improve this question

3 Answers 3

up vote 4 down vote accepted

If you have GNU grep, you can use Perl regular expressions, which have a negation construct.

grep -A1 -P '^(?!.*ignore me).*needle'

If you don't have GNU grep, you can emulate its before/after context options in awk.

awk -v after=3 -v before=2 '
/needle/ && !/ignore me/ {
    for (i in h) {
        print h[i];
        delete h[i];
    }
    until = NR + after;
}
{
    if (NR <= until) print $0; else h[NR] = $0;
    delete h[NR-before];
}
END {exit !until}
'
share|improve this answer

You appear to be using GNU . With GNU grep, you could pass in the --perl-regex flag to activate PCRE and then supply a negative lookahead assertion, example below

grep --after-context=1 \
--perl-regex '^(?:(?!ignore me).)*needle(?:(?!ignore me).)*$' file.txt
four needle
five

The main thing of note here is that (?:(?!STRING).)* is to STRING as [^CHAR]* is to CHAR

share|improve this answer
    
@1_CR... Sir.. it's awesome.. :P something smiler to ack –  Rahul Patil Oct 17 '13 at 15:40
    
@RahulPatil. :-), yes GNU grep is that good. –  1_CR Oct 17 '13 at 15:49
    
That's not quite what I want. I want it to work whether "ignore me" is before or after "needle". –  Flimm Oct 17 '13 at 15:57
    
@RahulPatil, thanks, i fixed it in the latest version –  1_CR Oct 17 '13 at 16:17

I would suggest using awk instead as it handles multi-line IO better. Either 1) Pipe the results to GNU awk with --\n as the record separator, or 2) Do all of the matching in awk.

Option 1

<file grep -A1 needle | awk '!/ignore me/' RS='--\n' ORS='--\n'

Output:

four needle                                                                                  
five
--

Note, this option searches the whole record for ignore me, set FS=1 and match against $1 to only compare to the first line.

Option 2

<file awk 'a-- > 0; $0 ~ re1 && $0 !~ re2 { print $0; a=after }' re1=needle re2='ignore me' after=1
share|improve this answer
    
Is there multiple ignore me in file then , awk not works –  Rahul Patil Oct 17 '13 at 16:22
    
@RahulPatil: could you rephrase or add more detail to your question? I don't understand what you are asking. –  Thor Oct 17 '13 at 19:05
    
@Thos test your example with this input file paste.ubuntu.com/6252860 –  Rahul Patil Oct 17 '13 at 19:41
    
@RahulPatil: I see what you mean now, Option 1 assumes that a --\n delimiter is between each matched group, which is not there if the groups are adjacent to each other. How adjacent groups should be handled is task-specific, so this is not necessarily wrong. Option 2 does not depend on the separator and is not affected. –  Thor Oct 17 '13 at 20:59

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.