15

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 
0

5 Answers 5

13

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=1 -v before=0 ' start <= until { start = until + 1; } /needle/ && !/ignore me/ { for (i = start; i < NR; i++) { print h[i]; delete h[i]; } until = NR + after; start = until; } { if (NR <= until) print $0; else h[NR] = $0; start = NR - before + 1; delete h[start-1]; } END {exit !until} ' 
1
  • You'd need to initialise start to 1 or you'd get an empty line if the first line matches. Commented Sep 18 at 11:47
9

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 Commented Oct 17, 2013 at 15:40
  • @RahulPatil. :-), yes GNU grep is that good. Commented 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". Commented Oct 17, 2013 at 15:57
  • @RahulPatil, thanks, i fixed it in the latest version Commented 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. Commented Mar 31, 2016 at 14:01
3

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 Commented 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. Commented Oct 17, 2013 at 19:05
  • @Thos test your example with this input file paste.ubuntu.com/6252860 Commented 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. Commented Oct 17, 2013 at 20:59
1

Disclaimer: this solution won't display the line after the matched line if it happens to contain ignore me see comment by @Filmm

A possible solution invoking grep multiple times is a simple modification of the naive solution presented by the OP

cat file | grep --after-context=1 needle | grep -v 'ignore me' | grep --after-context=1 needle 

Output:

four needle five 
2
  • 1
    Thanks for this answer! The problem with this technique is that it won't display the line after the matched line if it happens to contain ignore me. Commented Sep 19, 2024 at 10:31
  • True. This is only useful if one is not interested in ignore me even if it is part of the context... I'll leave the answer because it might be relevant for someone. But could you please downvote it? I can't Commented Sep 19, 2024 at 12:09
1

grep -v 'ignore me' file | grep -A 1 'needle'

yields

four needle five 

When I run it--a slightly shorter version of RMS's answer, but it sounds like you're looking for something more like:

COMMAND << EOF one needle ignore me two three needle four needle ignore me five six needle seven EOF 

That would give

three needle four needle ignore me --- six needle seven 

EDITED 20250912 0019Z: added --

grep -A 1 -- "$(grep -v 'ignore me' file | grep 'needle' | sed -z 's:\n[^$]:\\|:g')" file

  1. Find all lines that do not match 'ignore me'
  2. Find all lines in the output of 1 that match 'needle'
  3. Replace all \n except the final \n with \| in the output of 2
  4. Use the output of 3 as a pattern to match in file

This yields the expect result

three needle four needle ignore me --- six needle seven 

You probably want to go with one of the awk answers, but this seems to work.

3
  • The problem with the first solution is that it won't display the line of context if it happens to contain ignore me. The second solution will not handle special characters correctly, they need to be escaped to avoid being interpreted as regex directives. Commented Sep 10 at 20:11
  • You also need to make sure that the pattern passed to grep does not start with a dash, so that grep does not interpret that argument as a command-line option. Another way to avoid this problem is to use -- like this: grep -A 1 -- "$pattern" "$file1" "$file2" , when using -- , all arguments following it can start with a dash without them being interpreted as command-line options. Commented Sep 10 at 20:13
  • @Flimm The -- is a good catch. As for escaping special characters I think it's reasonable to say GIGO at some point. Commented Sep 12 at 0:25

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.