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.
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
-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
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.
-d '\n'
to xargs.
-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.
Here's the equivalent ack 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.
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:
xargs flags:
Credit to answer by @Hasturkun here
Pipe one grep
into another:
grep "string1" /path/to/files/* | grep "string2"
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.
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.
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.
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
It will find all the files, which contain three words(Status, ACTIVE, INACTIVE)
grep -l Status $(grep -l ACTIVE $(grep -r -l INACTIVE .))
$(...)
. 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