3

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?

  • 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. – Stephen Kitt Sep 18 '17 at 14:55
  • Yes, that is possible. Would need another loop in the testScripts.sh, but that is acceptable. – rugk Sep 18 '17 at 15:42
4

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).

  • 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. – rugk Sep 18 '17 at 15:44
  • Unfortunately, this results in a syntax error. And I see two things I don't understand: 1. Is not the "for loop" too short. It should be "for x in y", should not it? 2. Why do you have sh another time at the end? Syntax error: sh: -c: line 0: syntax error near unexpected token ./testScripts.sh' sh: -c: line 0: for n; ./testScripts.sh "$n" || exit 1; done' – rugk Sep 18 '17 at 15:55
  • @rugk Sorry, there was a do missing. Fixed now. – Kusalananda Sep 18 '17 at 15:59
  • @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 Sep 18 '17 at 16:00
  • @rugk See also update. – Kusalananda Sep 18 '17 at 16:03
1

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=$?
}
  • 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. – rugk Sep 18 '17 at 21:03
  • @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) – Stéphane Chazelas Sep 18 '17 at 21:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.