3

I have a folder tree of text files and I would like to find instances of a substring and the names of the files they came from.

If I do something like:

find . | xargs cat | grep 'abc'

then I would find instances of the substring, but not the files they originally came from.

Thanks for the help!

3
  • 1
    just use grep -r, or grep -rl which are supported on Linux, *BSD, and Solaris (via ggrep).
    – user313992
    Mar 27, 2020 at 20:50
  • @mosvy as I read it the OP wants both the filename and the matching line. -l only gives the filename...
    – xenoid
    Mar 27, 2020 at 22:27
  • 1
    @xenoid that's why I had written "grep -r or grep -rl"
    – user313992
    Mar 27, 2020 at 22:28

5 Answers 5

8

Running xargs cat like that loses the filenames and there's no good way to pass them through the pipeline at the same time as the data flows through.

But grep -l lists the names of files with matching strings, so you could use that:

find . -type f | xargs grep -l hello

Or with just having grep -r recursively dig through the directory, also resolving issues the issues xargs has with filenames containing white space or quotes:

grep -lre abc .

If you wanted the matching strings too, and not just the filenames, remove the -l to get the usual grep behaviour. With -r, it should print the matching filenames too even though we only give one path on the command line.

5
  • what about spaces in filenames?
    – user313992
    Mar 27, 2020 at 20:52
  • 1
    @mosvy, that's what "odd filenames" was supposed to mean, sorry for the lax phrasing
    – ilkkachu
    Mar 27, 2020 at 20:59
  • 1
    I am surprised that the OP accepted this answer, because the question says “I would like to find instances of a substring and the names of the files they came from”, but this shows only the filenames and not the ‘‘instances’’. Mar 27, 2020 at 22:31
  • 1
    @G-ManSays'ReinstateMonica' so.. removing the -l will work. I'd add -H if the filename wasn't showing. Mar 28, 2020 at 4:20
  • It so happens in my use case there aren't going to be multiple instance per file, so this was the most helpful answer to me, YMMV. Thanks to everyone who contributed!
    – ak0000
    Mar 29, 2020 at 9:56
7

xargs is superfluous. When you do xargs cat | grep, grep only sees a nameless input stream that contains the concatenated content of all the files. Do instead:

find . -type f -exec grep -H 'abc' {} +

This will call grep on each file (and minimizes the number of instances of grep, by calling grep with several files as parameters).

3
  • If you're using GNU grep (because of the -H flag), you might just as well dispense with find and use grep -r. But if you have to use find (eg. to filter files by mtime), I think you can make that portable with find . -type f -exec grep 'abc' /dev/null {} +
    – user313992
    Mar 27, 2020 at 20:57
  • @mosvy Yes, the -H is to catch the possible singleton in the last grep call that would make grep not report the file name. The /dev/null trick is cool but it could baffle someone inheriting the script, so it's -H or a line of comment...
    – xenoid
    Mar 27, 2020 at 22:20
  • 2
    -H is not standard. And script inheritors are easily baffled by pretty much everything, I don't think that makes any difference ;-)
    – user313992
    Mar 27, 2020 at 22:21
3
find ./ -type f -exec grep -H abc {} \;

or

grep -R 'abc' ./
4
  • ditto
    – user313992
    Mar 27, 2020 at 20:59
  • My answer has been added. At 12:07:51 against 12:10:18
    – ipatev_nn
    Mar 28, 2020 at 8:35
  • Your answer is different, because it's using -exec ... \; instead of -exec ... + and so it will spawn a separate grep process for each file, instead of handling the files in batches of ~ > 1k files to grep. But my point was that -H is NOT a portable option of grep, and grep implementations which support -H also support -r or -R.
    – user313992
    Mar 28, 2020 at 12:18
  • But yes, all the answers to this Q are pretty lousy, yours is not particular in this regard.
    – user313992
    Mar 28, 2020 at 12:21
2

Grep takes multiple filenames and reads the files, and with multiple args it shows the filenames, so just pass the filename list direct.

Also, don't pass directories to grep. And use null-terminated filenames to avoid special characters in filenames.

find . -type f -print0 | xargs -0 grep 'abc'
2

Try awk instead of cat and grep

find . -type f | xargs awk '/abc/{printf "%s is from %s\n", $0, FILENAME}'

Or just use find and awk

find . -type f -exec awk '/abc/{printf "%s is from %s\n", $0, FILENAME}' {} +

You must log in to answer this question.

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