7

I'm trying to search for lines containing 'foo' but these lines must not contain 'bar'. What's a good way to search with these parameters with ack?

I can do:

ack "foo" | grep -v "bar"

but it doesn't keep the nicely layed-out and coloured output of ack. Also, grep doesn't have good support for Perl regexes, unlike ack.

DBedrenko
  • 4,871
  • 4
  • 38
  • 73
  • @TomFenech Yes, terribly sorry. I corrected that a couple of mins after posting the question by for some reason it didn't persist. – DBedrenko Aug 31 '14 at 09:48
  • You can not use `awk`? It will do this with ease. – Jotne Aug 31 '14 at 10:12
  • @Jotne `ack` is more suited for searching text files, particularly source code, and has a lot of features suited for this that make it a better tool for such tasks than `awk`. e.g. `awk` only supports POSIX ERE which is kind of a deal-breaker for me; on the other hand, `ack` supports Perl regex. – DBedrenko Aug 31 '14 at 10:18
  • 1
    Regex is not always important. I see again and again problem solved using `awk` without using regex. If your system does have `awk` try it out using `time` to see what is fastest like this `time awk '/foo/ && !/bar/' file` Then you can make a selection based on `portability` `easy to understand` and `speed` – Jotne Aug 31 '14 at 10:41

2 Answers2

8

You can use a slightly more complicated regular expression:

ack '^(?!.*bar).*(foo)' file

The (?!...) does a negative lookahead to ensure that "bar" is not on the line. Here's how it works:

  • ^ Start at beginning of the line.
  • (?!.*bar) Advance any number of characters until "bar" is found. If it is found, do not continue. Otherwise, return to the start of the line and continue.
  • .*(foo) Match (and capture) "foo" at any point between the beginning and end of the line.

Testing it out:

$ cat file
other foo bar
baz foo other
$ ack '^(?!.*bar).*(foo)' file
baz foo other
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • Unlike the alternative suggestion of @jcl's answer this highlights only the search term instead of the whole line. I can try make an `ack` wrapper out of this somehow so that I don't have to type out that long regex. Thanks! – DBedrenko Aug 31 '14 at 10:13
  • 1
    No problem. The reason that only the word is highlighted is because I have surrounded it in a capturing group `(foo)`. If you remove the parentheses, the whole line will be highlighted. – Tom Fenech Aug 31 '14 at 10:16
  • Please could you explain why lines with "bar" are ignored even if their position in the line is after "foo"? I thought that a lookbehind looks *behind* the match pattern. – DBedrenko Aug 31 '14 at 10:29
  • Sure, I have added some explanation to my answer. – Tom Fenech Aug 31 '14 at 10:39
5

One possibility is to use ack itself for the exclusion pass, and to reverse the order of invocation:

ack -v "bar" | ack "foo"

That will at least highlight the search term, although it won't get you quite the same formatting as just ack "foo" does.

Another possibility is to pack the two terms together in the same regex, using a negative lookahead for the exclusion term, as given in this answer:

Multiple patterns with ack-grep?

ack '^(?!.*bar).*foo.*$'

That will get you ack's per-file formatting, but the whole line will be highlighted as the search term.

Community
  • 1
  • 1
jcl
  • 681
  • 6
  • 5