How do I get the output and exit value of a subshell when using "bash -e"?
Solution 1:
$()
preserves the exit status; you just have to use it in a statement that has no status of its own, such as an assignment.
output=$(inner)
After this, $?
would contain the exit status of inner
, and you can use all sorts of checks for it:
output=$(inner) || exit $?
echo $output
Or:
if ! output=$(inner); then
exit $?
fi
echo $output
Or:
if output=$(inner); then
echo $output
else
exit $?
fi
(Note: A bare exit
without arguments is equivalent to exit $?
– that is, it exits with the last command's exit status. I used the second form only for clarity.)
Also, for the record: source
is completely unrelated in this case. You can just define inner()
in the outer-scope.sh
file, with the same results.
Solution 2:
See BashFAQ/002:
If you want both (output, and exit status):
output=$(command)
status=$?
A Special Case
Note about a tricky case with function local variables, compare the following code:
f() { local v=$(echo data; false); echo output:$v, status:$?; }
g() { local v; v=$(echo data; false); echo output:$v, status:$?; }
We'll get:
$ f # fooled by 'local' with inline initialization
output:data, status:0
$ g # a good one
output:data, status:1
Why?
When the output of a subshell is used to initialize a local
variable, the exit status is no longer of the subshell, but of the local
command, which is most likely to be 0
.
See also https://stackoverflow.com/a/4421282/537554
Solution 3:
#!/bin/bash
set -e
source inner-scope.sh
foo=$(inner)
echo $foo
echo "I thought I would've died :("
By adding echo
, the subshell does not stand alone (is not separately checked) and does not abort. Assignment circumvents this problem.
You can also do this, and redirect the output to a file, to later process it.
tmpfile=$( mktemp )
inner > $tmpfile
cat $tmpfile
rm $tmpfile