The $? Variable and Exit Statuses
In our previous discussion, we explored the subtleties between [[ ... ]]
and [ ... ]
in Bash scripting and when to use each. However, a common mistake is always using [[ ]]
or [ ]
with if
conditions, even when it's not appropriate. Remember, [[ ]]
and [ ]
are used for evaluating conditional expressions such as string comparisons, pattern matching, or file tests - not for checking the exit status of commands or functions.
Common Mistake: Misusing [[...]] or [...] to Check Command Exit Status
Let's consider an example to illustrate this point:
function myfunc() {
echo "myfunc ran"
[[ $# -eq 0 ]] && return 1 || return 0
}
if [[ myfunc ]]; then
echo "return success"
else
echo "return failure"
fi
What Is the Output?
return success
Explanation
In this example, myfunc
never runs in the if
condition. This is because [[ myfunc ]]
is evaluating the conditional expression with the string "myfunc"
, not executing the function. In Bash, any non-empty string inside [[ ]]
evaluates to true, so the then
branch is always executed, regardless of the function's existence or its return value.
Attempting to Fix with Command Substitution
Now, let's try using command substitution:
function myfunc() {
echo "myfunc ran"
[[ $# -eq 0 ]] && return 1 || return 0
}
if [[ $(myfunc) ]]; then
echo "return success"
else
echo "return failure"
fi
What Is the Output?
return success
Explanation
Here, $(myfunc)
executes myfunc
and captures its output, not its exit status. The if [[ ... ]]
condition now evaluates the output of myfunc
, which is the string "myfunc ran"
. Since this is a non-empty string, the condition evaluates to true, and "return success"
is printed.
However, this still doesn't correctly check the function's exit status.
The Correct Way to Check Function Exit Status
To properly check whether myfunc
succeeds or fails based on its exit status, you should call the function directly in the if
condition without [[ ]]
or [ ]
. In Bash, if
statements can directly evaluate the exit status of a command or function.
Corrected Script
function myfunc() {
echo "myfunc ran"
[[ $# -eq 0 ]] && return 1 || return 0
}
if myfunc; then
echo "return success"
else
echo "return failure"
fi
if myfunc "arg"; then
echo "return success"
else
echo "return failure"
fi
Output:
myfunc ran
return failure
myfunc ran
return success
Explanation
First
if myfunc; then
block:myfunc
is called without arguments, so$# -eq 0
is true.The function executes
return 1
, indicating failure.The
if
condition evaluates the exit status1
(non-zero), which is considered false."return failure"
is printed.
Second
if myfunc "arg"; then
block:myfunc
is called with one argument, so$# -eq 0
is false.The function executes
return 0
, indicating success.The
if
condition evaluates the exit status0
, which is considered true."return success"
is printed.
Key Takeaways
Use
if myfunc; then
to check a function's exit status.The
if
statement directly evaluates the exit status of the command or function.An exit status of
0
is true; any non-zero exit status is false.
Do not use
[[ ]]
or[ ]
when you intend to check the exit status of a command or function.[[ ]]
and[ ]
are for evaluating conditional expressions, not for executing commands.Placing a command inside
[[ ]]
or[ ]
does not execute it; it treats it as a string or expression.
Command substitution
$(...)
captures the output of a command, not its exit status.Using
$(myfunc)
inside[[ ]]
captures the output ofmyfunc
.If the output is non-empty, the condition will be true, regardless of the function's exit status.
What about using
[ ]
(thetest
command)?Similar to
[[ ]]
, usingif [ myfunc ]; then
checks if the stringmyfunc
is non-empty.It does not execute the
myfunc
function.
Using the
$?
Variable:Alternatively, you can capture the exit status using
$?
immediately after the function call. This method works but is less concise than calling the function directly in theif
condition.
myfunc
if [ $? -eq 0 ]; then
echo "return success"
else
echo "return failure"
fi
Conclusion
Bash has many such gotchas due itโs subtle nature. These gotchas can be significantly time consuming to debug if you donโt know them. Have you encountered other "gotchas" in Bash scripting?
Share them with usโweโd love โค to discuss more!