Command line wizardry: spaces in file names with find | grep | xargs

command line

I need a way to run grep on a list of files which may contain spaces in the file name. On a list of files with no spaces, it's pretty straight forward and easy. Also, I know how to deal with spaces in the file names for find and xargs. I'm just looking for a way to use grep with the -print0 command.

I'm looking for text inside the file, not in the filename…

This lists all files:

find * -print 0 | xargs -0 -i{} echo {}

but say I have to do a grep (or some other command) inbetween.

This lists files which contain "howdy doody" inside the file:

find * | xargs grep -l "howdy doody" | xargs -i{} echo {}

This doesn't work, grep doesn't know how to recognize null terminated lines. Did I miss something in grep's man page?

find * -print0 | xargs grep -l "howdy doody" | xargs -0 -i{} echo {}

Best Answer

Are you looking for "howdy doody" in the filename, or within the file?

# find files with "howdy doody" in the filename
find * -name "*howdy doody*" -print0 | xargs -0 ...

xargs is what you need to use to split the null-terminated output from find -print0. In your example, the echo is extraneous; don't bother with it.

# find files containing "howdy doody"
find * -print0 | xargs -0 grep -l "howdy doody"

# find files containing "howdy doody" and do further processing

# multiple xargs version
find * -print0 | xargs -0 grep -l "howdy doody" | xargs -i{} do-something "{}"

# "sh -c" version
find * -print0 | xargs -0 -i{} sh -c 'grep -l "howdy doody" "{}" && do-something "{}"'

# notes: 
#   "sh -c" allows us to run a complex command with a single xargs
#   "{}" handles spaces-in-filename
#   handles any &&, ||, | command linking

You can also run your command directly from the find with -exec. The difference is that find -exec runs the command once per file found; xargs adds filenames to the end of the command (up to system limit), so it runs the command fewer times. You can simulate this with xargs -n1 to force xargs to only run one command per input.

# grep once per file
find * -exec grep -l "howdy doody" {} \; | ...
Related Question