If all you need is a list of filenames containing text that matches a pattern, such as "zorro", and you intend to use those filenames with another program, then you can do something like the following to create a bash array containing the filenames, and avoid a lot of manual copy-pasting with the mouse.:
mapfile -d '' -t myfiles < <(grep -IFlZr zorro ./)
mapfile
is a bash built-in command that populates an array from stdin. In this case, stdin is coming from grep
running as a process substitution. I'm using -d ''
with mapfile
to tell it the input will be delimited by NUL characters, and -Z
with grep
so that it outputs a NUL-separated list of matching filenames. This will work with any filename, no matter what characters are in the name - including colons, spaces, newlines, and shell metacharacters.
You can see what's in the array with declare -p myfiles
, or use the array's elements as args to another program (e.g. printf '%s\'n' "${myfiles[@]}"
), or in a loop like for f in "${myfiles[@]}"; do echo "$f" ; done
.
Note: grep's -l
option will exit after the first match (same as the -m
option), which will speed up the search in files where the search string appears early in the file. In files where the pattern doesn't appear, it still has to read the entire file.
BTW, if the reason why you're using the -I
option is that you want to avoid binary files (like .pdf or .dvi output from TeX), then you can use find
instead of grep -r
. For example:
mapfile -d '' -t myfiles < <(find . -type f -name '*.tex' -exec grep -lZF zorro {} +)
If you need more complicated selection criteria, beyond what grep is capable of then you can use awk or perl or whatever instead of grep. e.g. if you wanted only filenames where "zorro" appeared on line 3 of the file:
mapfile -d '' -t myfiles < <(find . -type f -name '*.tex' \
-exec perl -n -e 'if ($. == 3) {
printf "%s\0", $ARGV if (/\Qzorro\E/);
close(ARGV);
}' {} +)
This is just a simple example - if you can think of a criterion and write it as a perl (or awk or python or whatever) script, then you can use it to selectively populate the array. You can use any command, or any long and complicated pipeline of commands, as long as it prints filename(s) to stdout separated by a NUL character.
Note: the close(ARGV)
close the current file and skips to the next, if any, whether the filename was printed or not. This means it never has to read more than three lines of any file, so will be a lot faster if you have a lot of large files to search.
Note 2: the \Q
and \E
is perl's way of doing a fixed-string search, similar to -F
with grep. If you were using awk, you could do something similar with the index()
function.
find
tofind
the files, andawk
to search within the files and print whatever you want when there's a match then your script will be vastly simpler. Ask a new question with concise, testable sample input and expected output if you want help with that.