3

I'm trying to search for files that contain 2 patterns. I'm guessing piping is the way to go but I'm doing something wrong, because the following doesn't work:

ack -l "pattern1" | ack -l "pattern2"

What am I missing?

Andy Lester
  • 91,102
  • 13
  • 100
  • 152
mrtnmgs
  • 1,402
  • 14
  • 26
  • Does it have to be `ack`? `awk`, `grep` or `sed` can offer the same. Also, related: [Searching multiple patterns (words) with ack?](http://stackoverflow.com/q/26275582/1983854) – fedorqui Mar 30 '15 at 19:36

2 Answers2

5

I take it from your question that you want files that contains pattern1 and also contain pattern2, even if they are on different lines.

Here's one way to do it:

ack -l pattern2 $(ack -l pattern1)

Here's another:

ack -l pattern1 | ack -l -x pattern2

The -x says "Get the list of files to search from standard input, as if I were the xargs program." (This is assuming you're using ack 2.x or higher)

Andy Lester
  • 91,102
  • 13
  • 100
  • 152
  • 1
    We added `-x` because the `xargs` way of thinking is so ubiquitous, and this pattern of `ack -l that $(ack -l this)` is so common, and the `-x` makes it a more straightforward pipeline. – Andy Lester Mar 30 '15 at 20:19
  • 2
    Perhaps add `-s` to the 1st snippet to silence the potentially confusing error message that would result from the 1st `ack` invocation not returning anything (in case of not matching). – mklement0 Mar 30 '15 at 20:55
  • Great! Thanks Andy! That 2nd way did it for me (with the first snippet I got a bunch of errors: "Unsuccessful stat on filename containing newline at /usr/local/bin/ack line XXX" – mrtnmgs Mar 31 '15 at 21:19
  • I removed the quotes from the first example that someone else had added earlier. – Andy Lester Mar 31 '15 at 21:42
2

Update: If the intent is to find both patterns anywhere in the file (not necessarily on the same line), Andy Lester's answer offers simple solutions.


-l outputs the filename rather than the matching line(s), so do not use -l in your first ack invocation (or else the second one will only receive the filename as its input).

However, as @Mr. Llama correctly points out, even that wouldn't work, because the 2nd ack would print - (stdin) in the event of a match, given that its input comes from a pipe rather than a file.

ack "pattern1" | ack -l "pattern2" # !! prints '-' in case of match

Generally, -l only makes sense with filenames as operands.

Aside from that, however, your command would only match if (at least one) single line contained both search terms.

If that is truly the intent, you could get away with a single invocation of ack, using regex alternation (the parentheses are used for visual clarity and aren't strictly necessary):

ack -l "(pattern1).*(pattern2)|(pattern2).*(pattern1)" file
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775