Understanding Bash Short-Circuiting

Understanding Bash short-circuiting

This is actually a very common Bash pitfall. It is not a bug.

returnNumber 0 evaluates to true, so the second block (joined by logical and &&) is evaluated as well to make sure the result of first && second is still true.

The second block outputs OK but evaluates to false, so now the result of first && second is false. This means that the third portion (joined by logical or ||) must be evaluated as well, causing NG to be displayed as well.


Instead of relying on && and ||, you should be using if statements:

if returnNumber 0; then
echo 'OK'
returnNumber 1
else
echo 'NG'
fi

tl;dr: Never use x && y || z when y can return a non-zero exit status.

Why use short-circuiting boolean logic rather than if/then/else in bash?

[[ ]] is built-in conditional syntax in ksh, adopted by bash, zsh and others. Think of it as a more powerful version of the [ command (which is a different name for the command also called test -- and to be clear, [ is a command, not syntax). It is not a flow control operator, and thus largely off-topic in a discussion about flow control.


some-command && other-command is short-circuiting boolean logic. That is to say, it's a statement that returns whether both commands return truthful values.

If and only if some-command succeeds, it is necessary to run other-command to know whether both commands succeed. Thus, as a side effect of this logic operator, it happens to behave in a manner similar to an if statement.

Similarly, some-command || other-command returns a value which indicates whether one of the commands or the other has succeeded. In order to determine if either of the two is true, it is only necessary to run the one on the right if the the one on the left fails; if the one on the left succeeds, then it is already known a priori that one of the two has succeeded.

This behavior, and the associated idioms, are common to all languages implementing short-circuiting boolean logic with a well-defined order of operations; no part of this is specific to bash.


So, why would you use the short-circuiting form? Terseness.

Why would you use the long form? Correctness, particularly in cases where one needs to combine operations. Consider:

foo && bar || baz

This looks much like:

if foo; then bar; else baz; fi

...however, it has a bug: If bar fails, then baz can be run even if foo succeeded. Thus, the if/then/else form is to be preferred unless one is very certain about desired behavior in the boolean-logic case.

... && ... || ... (short-circuiting boolean) not identical to if ...; then ...; else ...; fi

The && and || operators in the shell work with the return value (exit code) of each statement. In a compound statement within { }, the exit status of the last command is the important one.

[[ 1 -eq 1 ]] && { echo "good"; } || { echo "bad"; }

is equivalent to:

true && true || true

So true && true evaluates to true, and the shell "short-circuits" (i.e. doesn't evaluate) the { echo "bad"; } because true || anything is true.

[[ 1 -eq 1 ]] && { echo "good"; false; } || { echo "bad"; }

is equivalent to:

true && false || true

In this case, true && false evaluates to false, and the shell has to evaluate full expression.

The if/else code takes the first branch, because the exit code of [[ 1 -eq 1 ]] is 0 (the same as true in the shell).

Short circuiting to set variable defaults in a shell script

Did you indicate that the script should use a specific shell? I always defaul to use bash as so:

#!/bin/bash

Nice explanation of the concept here.

EDIT: Okay, I better understand the question now. The original poster wants to set default values if variables are not passed. Nice explanation on setting default variables here. Try this script:

#!/bin/bash

fruit=${1-apple}
vegetable=${2-green bean}

echo $fruit;
echo $vegetable;

assignment in bash short circuit one-liner

You can, but parentheses are not for grouping in the traditional sense. The commands inside parentheses run in a subshell, and any variable assignments in a subshell are local to that shell, invisible outside it.

Use braces instead; note that you need a semicolon to terminate the command before a closing brace if it appears on the same line.

x=''
y='aaa'
{ [ ! -z "$x" ] && { echo "using SGE ID" >&2; num="$x"; }; } ||
{ [ ! -z "$y" ] && { num="z$y" && echo "trying y"; }; }
echo "num : $num : $y"
exit 0

Why does not bash short-circuit work in this case?

You want to execute usage in case either 1st or 2nd condition are accomplished. For that, you can do:

[ -z $a ] || [ -z $b ] && usage

Test:

$ [ -z "$a" ] || [ -z "$b" ] && echo "yes"
yes
$ b="a"
$ [ -z "$a" ] || [ -z "$b" ] && echo "yes"
yes
$ a="a"
$ [ -z "$a" ] || [ -z "$b" ] && echo "yes"
$

sh boolean short circuit variable assignment

I guess you're trying to do this:

#!/bin/sh    

S=0
T=0

numargs=$#
printf "You passed %s arguments\n" "$numargs"

check_param () {
if [ "$1" = "$2" ]; then
return 1
else
return 0
fi
}

i=1
while [ "$i" -le "$numargs" ]; do
current_arg=$1
shift
case $current_arg in
(-s)
S=1 ;;
(-t)
T=1 ;;
(*)
set -- "$@" "$current_arg" ;;
esac
i=$((i+1))
done

printf "S flag: %s\n" "$S"
printf "T flag: %s\n" "$T"

if [ $# = 0 ]; then
printf "No arguments left.\n"
else
printf "Arguments left (%s):\n" "$#"
printf " %s\n" "$@"
fi

I called this script banana, chmod +x banana and:

$ ./banana
You passed 0 arguments
S flag: 0
T flag: 0
No arguments left.
$ ./banana one two -s three
You passed 4 arguments
S flag: 1
T flag: 0
Arguments left (3):
one
two
three

This will tell you whether an -s or -t option is given, and will leave the arguments on the arguments stack!

Why does bash not stop on error for failures in sequence of short-circuited commands?

The logic here is that your use of && already is error-checking. The same way bash doesn't treat a failure within an if condition as worth aborting, even with set -e.

When you wrap the commands in a parenthesis, you are actually running those commands within a subshell, so the script itself only sees the return of that subshell, ie: it isn't aware that && is involved at all, so it aborts as you expect.

Prevent short-circuiting in Bash

Using ; would just return the result of the last compgen. I still need the logical or of all the compgens to know if there was any match.

You can write logic to handle exit status yourself. A short version could look likethe following:

{ cmd1; ret=$?; cmd2; ! ((ret | $?)); }


Related Topics



Leave a reply



Submit