58

Consider the following code

outer-scope.sh

#!/bin/bash
set -e
source inner-scope.sh
echo $(inner)
echo "I thought I would've died :("

inner-scope.sh

#!/bin/bash
function inner() { echo "winner"; return 1; }

I'm trying to get outer-scope.sh to exit when a call to inner() fails. Since $() invokes a sub-shell, this doesn't happen.

How else do I get the output of a function while preserving the fact that the function may exit with a non-zero exit code?

88

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

  • Why is it that, even though $? contains the exit status of $() that the script does not exit automatically (given that -e is set)? EDIT: nevermind, I think you have answered my questions, thanks! – jabalsad Dec 2 '11 at 16:12
  • I'm not sure. (I haven't tested any of the above.) But there are some restrictions on -e, all explained in bash's manpage; also, if you are asking about echo $(), then it might be because the subshells' exit codes are ignored when the line - the echo command - has an exit code (usually 0) of its own. – grawity Dec 2 '11 at 18:10
  • 1
    Hmm, when I type if ! $(exit 1) ; then echo $?; fi, I get 0. Not sure if is the way to go if you need to preserve that exit value. – Ron Burk May 4 '17 at 2:08
  • @grawity - Does this work with Dash? I'm trying to work around some annoying Autoconf behavior (namely, Autoconf reporting success when Sun or IBM's compiler prints illegal option to the terminal). – jww Nov 8 '17 at 20:33
  • 3
    if ! output=$(inner); then exit $?; fi will exit with a return code of 0 because $? will give the return code of ! instead of the return code of inner. You could get the desired behavior with if output=$(inner); then : ; else exit $?; fi but that's obviously more verbose – SJL Apr 6 '18 at 14:56
24

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

  • 2
    While this didn't really answer the question, this came in useful to me today, so +1. – fourpastmidnight Jul 27 '16 at 1:32
  • 1
    The exit status for bash commands is always that of the last command executed. When we spend so much time in strongly typed languages it's easy to forget that "local" isn't a type specifier but just another command. Thanks for re-iterating this point here, helped me today. – markeissler Sep 17 '16 at 1:11
  • 2
    Wow, I ran into this exact issue just now and you cleared it up. Thanks! – krb686 Jan 7 '17 at 2:50
4
#!/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
  • Of course, the $tmpfile continues to exist in the second variant... – Daniel Beck Dec 1 '11 at 13:43

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.