How to get the exit code of commands started by find?

6,590

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, then find will eventually do so as well, but this does not cause find to exit early.


Regarding the questions in comment:

  1. for n; do ... ; done looks weird but makes sense when you realize that without anything to iterate over, the for loop will iterate over "$@" implicitly.

  2. The trailing sh at the end will be placed in $0 of the sh -c shell. The {} will be substituted by a number of pathnames. Without sh 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 the sh -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=$?
}
Share:
6,590

Related videos on Youtube

rugk
Author by

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, 2022

Comments

  • rugk
    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 to find 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
      Stephen Kitt over 6 years
      Can 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
      rugk over 6 years
      Yes, that is possible. Would need another loop in the testScripts.sh, but that is acceptable.
  • rugk
    rugk over 6 years
    Note 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
    Kusalananda over 6 years
    @rugk Sorry, there was a do missing. Fixed now.
  • Kusalananda
    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 last sh. The trailing sh will be placed in $0, which is not a positional parameter (in the same sense as $1 etc. are).
  • Kusalananda
    Kusalananda over 6 years
    @rugk See also update.
  • rugk
    rugk over 6 years
    Thanks 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, the find 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
    Stéphane Chazelas over 6 years
    Note that the || exit 1 exits that sh invocation but does not prevent more sh invocations from being started. With xargs, you can use the 255 exit status to cause xargs to abort. See also the -quit predicate of GNU find or -exit predicate (which can take an exit code as argument) of netbsd.
  • rugk
    rugk over 6 years
    Thanks 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
    Stéphane Chazelas over 6 years
    @rugk, see my comment though on @Kusalananda's answer, find may run more than one sh instance depending on how many files there are, the size of the environment, and the current limit on that stacksize (if on Linux)