Don't use xargs
on the output of find
. xargs
by default expects the arguments on input to be delimited in a format that no command outputs, certainly not find
. The output of find -print
is also not post-processable reliably.
The only way to use find
with xargs
sort-of-reliably is by using the -print0
and -0
non-standard extensions (initially from GNU find
/xargs
, but nowadays found in many other implementations).
GNU xargs
also has a -r
/ --no-run-if-empty
extension to not run the command if the input is empty (some xargs
implementations such as the one on NetBSD do that by default).
So here, with GNU find
/xargs
/grep
or compatible, you could do:
find trash.later -type f -print0 |
grep -z bar |
xargs -r -0 -n 1 ./dummy.sh
But that has no advantage over the standard:
find trash.later -path '*bar*' -type f -exec ./dummy.sh {} ';'
(where -path '*bar*'
looks for bar
anywhere in the file path like your grep bar
attempted to do. Replace with -name '*bar*'
to look for bar
in the file name only (the last component of its path)).
More reading on that at Why is looping over find's output bad practice?
As a more general answer to your question, for xargs
implementations that do not support -r
/ --no-run-if-empty
, the work around is to use a sh
wrapper.
Instead of:
... | xargs -n1 cmd
do:
... | xargs sh -c 'for file do cmd "$file"; done' sh
That still runs sh
if there's no input, but since the list of arguments is empty, there will be no pass in the loop, so cmd
won't be run.
See also the ifne
command from moreutils
which only runs the command if the input is non-empty
... | ifne xargs -n1 cmd
However note that if the input is not empty but only contains blank lines, ifne
will run xargs
but xargs
will still consider its input has no argument and still run cmd
once without argument (except on NetBSD).
For completeness
... | xargs -I {} cmd {}
will also not run cmd
if the input is empty / blank. In that case, the splitting of the input into arguments is different: leading blanks are removed, quotes are still processed (in xargs
's very own unique way), but otherwise arguments are whole lines instead of blank separated words.
for file in $1
would be wrong. That's applying split+glob on the contents of the first positional parameter. Possibly you meantxargs -n1 sh -c '[ "$#" -eq 0 ] || cmd "$1"' sh
, but it's wasting resource to run onesh
per argument whensh
is well able to process more than one at once. – Stéphane Chazelas Mar 15 '22 at 10:20find ... | xargs sh -c 'for file do cmd "$file"; done' sh
is not what you want to do, it will fail if file names contain blanks or newlines or quotes or backslashes... IMO thefind ... | xargs
idiom should be banned (at least when not using NUL-delimited records). – Stéphane Chazelas Mar 15 '22 at 10:25xargs -I {} ...
, right? – Erwann Mar 15 '22 at 17:11xargs -I {}
will still not work with filenames containing quotes, backslashes or newline (or that start with blanks, though that doesn't apply to your case where file paths start withtrash.later/
) – Stéphane Chazelas Mar 15 '22 at 18:01