10

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
7

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

  • @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. – iruvar 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 – iruvar Oct 17 '13 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 '16 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
  • 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

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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