grep With find and xargs for Large Trees
find selects files and xargs feeds them to grep, scaling a search beyond what a single recursive grep filters cleanly.
When file selection is more complex than --include can express (by mtime, size, or deep glob), find plus xargs plus grep is the portable combination. The NUL-delimited form keeps it safe.
What it does
find locates files by name, type, time, or size; -print0 outputs them NUL-separated; xargs -0 passes them to grep without word-splitting on spaces or newlines. grep then searches the batch. This composes filters that a single grep cannot, and handles arbitrarily many files by batching.
Common usage
# grep only files modified in the last day
find . -name '*.log' -mtime -1 -print0 | xargs -0 grep -n ERROR
# avoid "no such file" when find returns nothing
find . -name '*.go' -print0 | xargs -0 -r grep -l "panic"
# add filename even with a single match
find . -name '*.conf' -print0 | xargs -0 grep -H "listen"Options
| Piece | What it does |
|---|---|
| find ... -print0 | Emit NUL-separated filenames |
| xargs -0 | Read NUL-separated input safely |
| xargs -r | Do not run grep at all if input is empty (GNU) |
| grep -H | Force the filename prefix per match |
| grep -h | Suppress the filename when undesired |
In CI
Always use -print0 with xargs -0 so filenames with spaces survive, and add xargs -r (GNU) so an empty find result does not run grep against stdin and hang or error. For a single batch, grep with one file omits the filename, so add -H when you parse file:line output downstream.
Common errors in CI
Without -r, an empty find result makes xargs run grep PATTERN with no files, so grep reads stdin and the job hangs or fails; add xargs -r or find ... | grep differently. xargs splits on whitespace without -0, so paths with spaces break into wrong arguments. The overall exit status comes from grep on the last batch, so a no-match still yields exit 1; guard with || true when appropriate.