16

I want to find files containing two strings together, for example the file contains both string1 and string2.

I want the full path of files in output. I don't want to see "permission denied" warnings.

10 Answers 10

23
grep -l string2 `grep -l string1 /path/*`

which is the same as

grep -l string2 $(grep -l string1 /path/*)

Edit: heres why grep string1 /path/* | grep string2 doesn't do what I think alwbtc wants.

$ cd /tmp
$ cat a
apples
oranges
bananas
$ cat b
apples
mangoes
lemons
$ cat c
mangoes
limes
pears
$ cd ~
$ grep apples /tmp/* | grep mangoes
$

Nothing found, but file b contains both strings.

Here's what I think alwbtc wants

$ grep -l apples $(grep -l mangoes /tmp/*)
/tmp/b
4
  • 1
    This is a neat solution and more useful than mine. For those that want to know what is going on here (it took me a bit to figure out), he's using the -l option to return the file names instead of the lines. He's then using the dollar sign or the back quotes to pass that list as the FILE argument into the second grep. This allows the second grep to search the entirety of each file found instead of the individual lines as in my solution. Jun 18, 2012 at 14:12
  • You missed a -l option for both commands to be considered equal.
    – pong
    Sep 11, 2013 at 11:12
  • @embedded.kyle this doesn't work for filenames containing spaces or newlines
    – phuclv
    Mar 25, 2022 at 8:57
  • grep -l word1 $(grep -r -l word2 .) Aug 4, 2022 at 10:47
4

I was looking for an extensible way to do 2 or more strings and came up with this:

grep -rl string1 path-to-files | xargs grep -l string2 | xargs grep -l string3

The first grep recursively finds the names of files containing string1 within path-to-files.

The results are piped into xargs which runs one or more grep commands on those files for string2.

The results are then piped into another xargs command for string3 - it's the same as the first xargs call, but looking for a different string.

The use of xargs will avoid problems where there are so many results that the resultant command line from the use of back-ticks is too long.

To avoid the warnings we can redirect stderr to /dev/null:

grep -rl string1 path-to-files  2>/dev/null | xargs grep -l string2

It's not needed on the subsequent grep calls because string1 has already been found inside the file so the permissions are known to be good.

2
  • Nice solution. Especially if you work with a large data set and want to see results fast. But if you have white space in your filenames, add something like -d '\n' to xargs.
    – Robert
    Aug 10, 2020 at 12:39
  • 3
    If the filenames contain white-space it may be better to use -Z or --null as an argument to the grep calls and -0 (dash zero) to xargs. Filesnames are not allowed to contain the null character so it's a safe delimiter.
    – awatts
    Aug 10, 2020 at 13:51
2

Here's the equivalent command to RedGrittyBrick's answer:

ack string2 $(ack string1 -l)

Works the same way (except ack by default searches the current directory recursively). The contents within the $() searches for string1 but -l outputs only the filenames where that string was found. They are then passed as arguments into the outer command, which means string2 is searched for within those list of files only.

1
2

For filenames that include spaces I recommend:

grep -rlZ 'string1' /absolute/path/to/search/dir | xargs -0 grep -l 'string2'

If you want the output in a txt file you can add:

grep -rlZ 'string1' /absolute/path/to/search/dir | xargs -0 grep -l 'string2' > './paths.txt'

grep flags:

  • -r (--recursive) recursively search through all files in all directories under the provided file
  • -l (--files-with-matches) print just the path/filenames of the files without showing the actual match
  • -Z (--null) output NUL terminated file names

xargs flags:

  • -0 (--null) tells xargs to read NUL terminated arguments

Credit to answer by @Hasturkun here

1

Pipe one grep into another:

grep "string1" /path/to/files/* | grep "string2"

3
  • 10
    If the two strings are on different lines in the file, this wont work. Jun 15, 2012 at 20:01
  • 1
    I didn't know there was a requirement for them to be in the same line @RedGrittyBrick
    – slhck
    Jun 15, 2012 at 21:35
  • @slhck: I've updated my answer to show what I think alwbtc wants and why this answer doesn't do that. Of course, I may have misunderstood what alwbtc wants and embedded.kyle may have got it right - I suspect not though. Jun 15, 2012 at 22:28
1

You can use find.

-exec in find is usually interpreted as an action, but it's also a test. Such test is true iff what you execute returns exit status 0. grep returns exit status 0 iff there is a match. This is how you use grep in such tests:

find . -type f -exec grep -q string1 {} \; -exec grep -q string2 {} \; -print

Notes:

  • string1 and string2 are patterns. Use grep -F for fixed strings.

  • The solution is portable.

  • It's dead easy to add more tests with grep.

  • It's easy to add more arbitrary tests.

  • It's possible to build complex logic (see "Theory" in this other answer).

  • The solution works well with pathnames with spaces, newline characters etc. Ambiguity may arise when a pathname containing newline is printed, because -print terminates pathnames with newlines; but this is a disadvantage of -print and therefore in general you shouldn't parse what it prints.

    -print0 instead of -print will allow you to pipe to xargs -0 and to do something with matching files. find … -print0 | xargs -0 … is a common way to handle arbitrary pathnames, not portable though. While you can replace non-portable -print0 with portable -exec printf '%s\0' {} +, there is no portable equivalent of xargs -0. The right portable way to do something with matching files is to add another -exec after all the tests.

  • The easiest way to hide permission denied is to redirect stderr to /dev/null (find … 2>/dev/null), but then you won't see other errors or warnings.

0
comm -12 <(grep --fixed-strings --files-with-matches "STRING1" /path/to/files/* 2>/dev/null | sort) <(grep --fixed-strings --files-with-matches "STRING1" /path/to/files/* 2>/dev/null | sort)

or less redundantly:

search_files () { str="$1"; shift; grep -Fl "$str" "$@" 2>/dev/null | sort; }
comm -12 <(search_files "STRING1" /path/to/files/*) <(sf "STRING2" /path/to/files/*)

This will work if the strings are on different lines of the same file and will also avoid false positives if a filename contains one of the strings.

0

To elaborate on @RedGrittyBrick's solution which has a shortcoming when running the command unattended plus to suppress error output as intended and to find files recursively you might consider

grep -l 'STRING1' $(! grep -lrs 'STRING2' /absolute/path/to/search/dir && echo /dev/null)

-s option will suppress error messages
-r option allows to search for strings in arbitrarily nested directories
! combined with && echo /dev/null guarantees that the command won't hang up. Otherwise, if the inner grep doesn't find any file it won't output anything so that the outer grep will indefinitely wait for input to search upon. This solution outputs /dev/null in these cases so outer grep will search for STRING1 in /dev/null where it's supposed to not finding anything.

0

The way I usually do this is with -C numberOfLines in GNU or BSD grep:

grep -C 9999 STRING1 | grep STRING2

What -C does in this case is show context of 9999 lines before and after every hit for STRING1. If that context contains STRING2 then the second grep after the pipe will get it. Replace 9999 with whatever big number you like. It's a little hacky but always works for me.

More on the -C command here: https://stackoverflow.com/questions/9081/grep-show-lines-surrounding-each-match

1
  • How does this command find files? I mean if I have many files in a directory, how can your command tell me the paths to the files that contain both strings? The OP explicitly asks for paths. Aug 30, 2022 at 21:37
0

It will find all the files, which contain three words(Status, ACTIVE, INACTIVE)

grep -l Status $(grep -l ACTIVE $(grep -r -l INACTIVE .))
1
  • Like some other answers here, this relies on the shell splitting words from unquoted $(...). If pathnames contain spaces then this approach will fail miserably. If they contain wildcard characters (especially *) then it may give you false results. Nov 14, 2022 at 7:51

You must log in to answer this question.

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