If on Linux, you could run the command under strace -fe process
to know which process did an exit_group(126)
and what command it (or any of its parent if it didn't execute anything itself) executed last before doing that:
$ strace -fe process sh -c 'env sh -c /; exit'
execve("/bin/sh", ["sh", "-c", "env sh -c /; exit"], [/* 53 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7f24713b1700) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24713b19d0) = 26325
strace: Process 26325 attached
[pid 26324] wait4(-1, <unfinished ...>
[pid 26325] execve("/usr/bin/env", ["env", "sh", "-c", "/"], [/* 53 vars */]) = 0
[pid 26325] arch_prctl(ARCH_SET_FS, 0x7fbdb4e2c700) = 0
[pid 26325] execve("/bin/sh", ["sh", "-c", "/"], [/* 53 vars */]) = 0
[pid 26325] arch_prctl(ARCH_SET_FS, 0x7fef90b3b700) = 0
[pid 26325] clone(strace: Process 26326 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fef90b3b9d0) = 26326
[pid 26325] wait4(-1, <unfinished ...>
[pid 26326] execve("/", ["/"], [/* 53 vars */]) = -1 EACCES (Permission denied)
sh: 1: /: Permission denied
[pid 26326] exit_group(126) = ?
[pid 26326] +++ exited with 126 +++
[pid 26325] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 126}], 0, NULL) = 26326
[pid 26325] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26326, si_uid=10031, si_status=126, si_utime=0, si_stime=0} ---
[pid 26325] exit_group(126) = ?
[pid 26325] +++ exited with 126 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 126}], 0, NULL) = 26325
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26325, si_uid=10031, si_status=126, si_utime=0, si_stime=0} ---
exit_group(126) = ?
+++ exited with 126 +++
Above, that was process 26326 that first exited with 126, that was because it attempted to execute /
. It was a child of process 26325 that last executed sh -c /
.
If those scripts are bash
scripts or if they are sh
scripts and sh
happens to be bash
on your system, you could do:
$ env SHELLOPTS=xtrace \
BASH_XTRACEFD=7 7>&2 \
PS4='[$?][$BASHPID|${BASH_SOURCE:-$BASH_EXECUTION_STRING}|$LINENO]+ ' \
sh -c 'env sh -c /; exit'
[0][30625|env sh -c /; exit|0]+ env sh -c /
[0][30626|/|0]+ /
sh: /: Is a directory
[126][30625|env sh -c /; exit|0]+ exit
That doesn't tell us exactly what process exited with 126 but could give you enough clue.
We use BASH_TRACEFD=7 7>&2
so that the traces are output on the original stderr, even when stderr is redirected within the scripts. Otherwise those trace messages could affect the behaviour of the scripts if they do things like (....) 2>&1 | ...
. That assumes those scripts don't explicitly use or close fd 7 themselves (that's would be unlikely, a lot more unlikely than them redirecting stderr).
You can't tell. All you get is a single value between 0 and 255, which is 0 if everything went well and nonzero otherwise.
If you want to treat some nonzero statuses as successes, be sure that the command in question can't fail for other reasons such as a redirection. Break up the command so that different kinds of failures happen in different commands or result in different statuses.
For example, if you need to know whether an error comes from a redirection, either do the redirection in a separate command, or do it separately over a block.
Combined status:
mycommand <foo
status=$?
if [ $status -ne 0 ]; then echo "Either mycommand failed or <foo failed"; fi
Separate statuses, but no way to avoid running the command if the redirection fails:
{
mycommand
command_status=$?
} <foo
redirection_status=$?
if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi
if [ $redirection_status -ne 0 ]; then echo "<foo failed"; fi
Do the redirection first. Note that being able to react to the failure of the redirection in this way is a bash feature. POSIX shells, including bash in POSIX mode, exit if a redirection on the exec
builtin fails.
exec 3<&1 # Save stdin to file descriptor 3
exec <foo # bash keeps going if the redirection fails
redirection_status=$?
mycommand
command_status=$?
exec <&3 # Restore stdin
if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi
if [ $redirection_status -ne 0 ]; then echo "<foo failed"; fi
Do the redirection fails, in a subshell to contain the failure of the redirection and not have to restore the file descriptor afterwards.
(
exec <foo || exit $? # In POSIX sh, "|| exit $?" is redundant.
mycommand
command_status=$?
if [ $command_status -ne 0 ]; then echo "mycommand failed"; fi
)
redirection_status=$?
if [ $redirection_status -ne 0 ]; then echo "<foo failed and mycommand didn't run"; fi
If you need to know whether an error comes from some other expansion, do the expansion separately and save its result.
Saving one argument: instead of `mycommand "$(…)", save the result of the expansion first.
foo=$(…) && mycommand "$foo"
More generally:
foo=$(…)
command_substitution_status=$?
mycommand "$foo"
mycommand_status=$?
Note that if an assignment contains multiple command substitutions, its status is the status of the last substitution: the status is 0 if the last substitution succeeds, even if earlier ones failed.
foo=$(…)
foo_status=$?
bar=$(…)
bar_status=$?
mycommand "$foo" "$bar"
mycommand_status=$?
To save multiple arguments, use an array, or the positional parameters inside a function.
args=()
foo=$(…)
foo_status=$?
args+=(-x "$foo")
bar=$(…)
bar_status=$?
args+=(-y "$bar")
mycommand "${args[@]}"
Best Answer
Using Stephen Kitt's suggestion in comments:
This will cause the
sh -c
script to exit with a non-zero exit status as soon astestScript.sh
does. This means thatfind
will also exit with a non-zero exit status: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).