How to get the exit code of commands started by find?
Solution 1
Using Stephen Kitt's suggestion in comments:
find . -type f -iname "*.sh" -exec sh -c 'for n; do ./testScripts.sh "$n" || exit 1; done' sh {} +
This will cause the sh -c
script to exit with a non-zero exit status as soon as testScript.sh
does. This means that find
will also exit with a non-zero exit status:
If terminated by a plus sign, the pathnames for which the primary is evaluated are aggregated into sets, and utility will be invoked once per set, similar to
xargs(1)
. If any invocation exits with a non-zero exit status, thenfind
will eventually do so as well, but this does not causefind
to exit early.
Regarding the questions in comment:
for n; do ... ; done
looks weird but makes sense when you realize that without anything to iterate over, thefor
loop will iterate over"$@"
implicitly.The trailing
sh
at the end will be placed in$0
of thesh -c
shell. The{}
will be substituted by a number of pathnames. Withoutsh
there, the first pathname would end up in$0
and would not be picked up by the loop, since it's not in$@
.$0
usually contains the name of the current interpreter (it will be used in error message produced by thesh -c
shell).
Solution 2
xargs
will exit with an exit status between 1 and 125 (123 with GNU xargs
), if any of the command fails, and will abort if any fails with a 255 status.
To use xargs
reliably on the output of find
(with -print0
) and preserve the command's stdin, you'd need GNU xargs
though. So, with GNU xargs
and a shell with support for process substitution like ksh
, zsh
or bash
:
xargs -n1 -r0a <(find . -type f -iname '*.sh' -print0) sh ./testScripts.sh
Or to abort at the first failing one:
xargs -r0a <(find . -type f -iname '*.sh' -print0) sh -c '
for file do
sh ./testScripts.sh "$file" || exit 255
done' sh
You can also abort find
upon the first error with (POSIX code):
find . -type f -name '*.[sS][hH]' -exec sh -c '
for file do
if ! sh ./testScripts.sh "$file"; then
kill -s PIPE "$PPID"
exit 1
fi
done' sh {} +
(using SIGPIPE as a less noisy signal with some shells like bash
). That will cause find
to be killed and so return with a non-zero exit status.
To get the exact value of the exit status of the (here last) failing commmand, with zsh
or bash
, you can also do:
ret=0
while IFS= read -rd '' -u3 file; do
sh ./testScripts.sh "$file" 3<&- || ret=$?
done 3< <(find . -type f -iname '*.sh' -print0)
Though with zsh
, you don't even need find
for that:
set -o extendedglob
ret=0
for file (./**/*(#i).sh(D.)) {
./testScripts.sh $file || ret=$?
}
Related videos on Youtube
rugk
Apparently, this user prefers to keep an air of mystery about them. … I mean, yes I do. There is also not a lot to tell… You can follow me on Mastodon or check out my GitHub or GitLab profile.
Updated on September 18, 2022Comments
-
rugk over 1 year
I am using "find" in a Travis-CI to check a particular file type with a program. (To be exact, it is a shellcheck check.)
However, when using
find
the exit codes of the command(s)/subshells executed by it are naturally discarded, as they are not passed to the "main script".As an example this is a find command:
find . -type f -iname "*.sh" -exec sh ./testScripts.sh "{}" \;
./testScripts.sh
may exit with 0 or >= 1, depending on the test result.The
testScripts.sh
exits properly with the correct exit code, but due tofind
the exit code of the command is always "0". All I want is, that if one file/execution errors, this error is "propagated" up to Travis-CI.How can I accomplish this?
-
Stephen Kitt over 6 yearsCan you change
testScripts.sh
so it accepts multiple scripts to run? That way you could use the-exec {} +
variant, which exits with status != 0 if the command fails. -
rugk over 6 yearsYes, that is possible. Would need another loop in the
testScripts.sh
, but that is acceptable.
-
-
rugk over 6 yearsNote the "this does not cause find to exit early.". This is important to know. Not that it matters in my case (it may actually be a good thing), but just FYI.
-
Kusalananda over 6 years@rugk Sorry, there was a
do
missing. Fixed now. -
Kusalananda over 6 years@rugk
for n; do ...; done
will iterate over the positional parameters. These are given by{}
on the command line after that lastsh
. The trailingsh
will be placed in$0
, which is not a positional parameter (in the same sense as$1
etc. are). -
Kusalananda over 6 years@rugk See also update.
-
rugk over 6 yearsThanks for the update. It works. One thing I noticed: In this way, it actually does exit early when an exit code is returned in any of the
./testScripts.sh
executions. Also I like the_
way more, thanks for the explanation though… Some final questions though: 1. As having everything in one line may not be nice, is it be possible to move the "for" loop into the./testScripts.sh
file? 2. When./testScripts.sh
returns an error code > 1, thefind
output is always "1". Even if I change the line to "exit $?" – it does not work. Can I keep the "correct" exit code, somehow? -
Stéphane Chazelas over 6 yearsNote that the
|| exit 1
exits thatsh
invocation but does not prevent moresh
invocations from being started. Withxargs
, you can use the 255 exit status to causexargs
to abort. See also the-quit
predicate of GNUfind
or-exit
predicate (which can take an exit code as argument) of netbsd. -
rugk over 6 yearsThanks for your answer. Basically it seems to use the same concept as @Kusalananda's answer, but this here looks a bit more complicated. Anyway, good to have alternatives.
-
Stéphane Chazelas over 6 years@rugk, see my comment though on @Kusalananda's answer,
find
may run more than onesh
instance depending on how many files there are, the size of the environment, and the current limit on that stacksize (if on Linux)