With GNU implementations of those utilities (and a shell with support for ksh-style¹ process substitution), you could just do:
xargs -r0a <(grep -rlZ --include='*.[hc]pp' FOO) cmd --
Above, with -r
(aka --recursive
) grep
does find
's job (beware in current versions of grep
, it behaves as if -type f
was passed to the equivalent find
command), and passes the l
ist of file paths NUL-delimited (-Z
) to xargs
via a pipe whose path is passed to -a
(aka --arg-file
).
If you still wanted to use find
(for instance to apply more complex file criteria than file name suffix), you'd do:
xargs -r0a <(
find . -name '*.[hc]pp' -type f -exec grep -lZ FOO {} +
) cmd
(here the --
is not necessary as the file paths will start with ./
, so not -
nor +
).
Doing find ... -exec grep -q FOO {} \; -exec cmd {} +
works but means forking a process, executing grep
, which involves loading and linking shared libraries for each file which is going to be orders of magnitude more expensive than what grep
needs to do here (read a few KiBs of text and find FOO
within), so is best avoid wherever possible if performance or resource usage is a concern.
If you're not on a GNU system², but your find
supports -print0
and xargs
supports -r
and -0
as will be required in the next version of the POSIX standard, you can do the reporting of file paths with perl
:
find . -name '*.[ch]pp' -type f -print0 |
xargs -r0 perl -0lne 'if (/FOO/} {print $ARGV; close ARGV}' |
xargs -r0 cmd
Though that assumes cmd
does not read from its stdin (which here, depending on the xargs
implementation will be either opened on /dev/null or the reading end of the pipe perl
is writing to, inherited from xargs
).
¹ Including ksh implementations other than those based on pdksh and zsh and bash; change the <(...)
to <{...}
in rc-like shells, and (...|psub)
in fish
² Note however that GNU grep
used to be used on BSDs (and still is in some), and some BSDs that have moved away from GNU grep
have replicated its API, so you'll find non-GNU implementations of grep
that do support its -Z
non-standard option; sometimes only the --null
long-option equivalent like on OpenBSD (where -Z
is for something else), FreeBSD or MacOS.
+
to\; | xargs cmd
is enough. I should learn how to use thexargs
beast :/