12

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

3 Answers 3

12

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}
'
8

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

5
  • @1_CR... Sir.. it's awesome.. :P something smiler to ack Oct 17, 2013 at 15:40
  • @RahulPatil. :-), yes GNU grep is that good.
    – iruvar
    Oct 17, 2013 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, 2013 at 15:57
  • @RahulPatil, thanks, i fixed it in the latest version
    – iruvar
    Oct 17, 2013 at 16:17
  • Very useful. Especially in the case of grep with context where you wish to exclude closely matching lines but without a certain part of the pattern. Close to original question but not quite the same.
    – gaoithe
    Mar 31, 2016 at 14:01
2

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
4
  • Is there multiple ignore me in file then , awk not works Oct 17, 2013 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, 2013 at 19:05
  • @Thos test your example with this input file paste.ubuntu.com/6252860 Oct 17, 2013 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, 2013 at 20:59

You must log in to answer this question.

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