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). – mosvy Mar 27 '20 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 '20 at 22:27
  • 1
    @xenoid that's why I had written "grep -r or grep -rl" – mosvy Mar 27 '20 at 22:28
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? – mosvy Mar 27 '20 at 20:52
  • 1
    @mosvy, that's what "odd filenames" was supposed to mean, sorry for the lax phrasing – ilkkachu Mar 27 '20 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’’. – G-Man Says 'Reinstate Monica' Mar 27 '20 at 22:31
  • 1
    @G-ManSays'ReinstateMonica' so.. removing the -l will work. I'd add -H if the filename wasn't showing. – tedder42 Mar 28 '20 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 '20 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 {} + – mosvy Mar 27 '20 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 '20 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 ;-) – mosvy Mar 27 '20 at 22:21
3
find ./ -type f -exec grep -H abc {} \;

or

grep -R 'abc' ./
4
  • ditto – mosvy Mar 27 '20 at 20:59
  • My answer has been added. At 12:07:51 against 12:10:18 – PotroNik Mar 28 '20 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. – mosvy Mar 28 '20 at 12:18
  • But yes, all the answers to this Q are pretty lousy, yours is not particular in this regard. – mosvy Mar 28 '20 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}' {} +

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.