Bash Script – Logical AND (&&) and OR (||) Paradox Explained

bashexit-statusshell-script

So I well understand that exit code of 0 is considered success in running of a program. Then we use this in bash scripts with logical AND and OR to run the next program based on the exit status of the first program. A good example can be found here: https://unix.stackexchange.com/a/187148/454362

Does this mean 0 is interpreted as true and any other number but zero is interpreted as false? This is contrary to all the programming languages that I know of. So can I assume that bash internally uses logical NOT to reverse the exit code to proper false / true value?

So, these are some examples:

#should result in false, and bash shows 0 as false
echo $(( 1 && 0 ))

# should result in true,  and bash shows 1 as true
echo $(( 1 || 0 )) 

# does not show 'hi'; even though if false is zero per first example, then
# this should be considered success exit code and 'hi' to be shown!!
false && echo 'hi' 

# does show 'hi'. It first runs the "echo 'hi'" and interprets its exit
# status of zero as success, but then false stops the 2nd hi.
echo 'hi' && false && echo ' 2nd hi' 

# shows 'hi 2nd hi'
echo 'hi' && (( 1 || 0 )) && echo ' 2nd hi'

It seems I am answering my own question. But I just want clarification if someone knows the internal of the bash processing.

Best Answer

The examples in the post you linked to are things like:

false && echo "OK"
true || echo "OK"

In that context, with the exit status of a processes, yes, 0 is truthy, and anything else is falsy. (Yes, true and false are programs here. Probably builtin to the shell, but that works the same.)

From the POSIX definition (|| is similar, as is what Bash's documentation says):

AND Lists
The control operator "&&" denotes an AND list. The format shall be:
command1 [ && command2] ...
First command1 shall be executed. If its exit status is zero, command2 shall be executed, and so on, ...

Yes, that's opposite to the conventions used in pretty much all other contexts and programming languages. Then again, it is also somewhat common in C for functions to return a zero on success, and a non-zero error code on error. That way, you can tell different sorts of error apart(*). That, of course, doesn't really work if your function needs to return some useful value, like a pointer, and as far as the C language is concerned, 0 is falsy. Anyway, different it is.

I don't think it's a good idea to assume anything about the implementation. Just remember that in this context zero is true, and e.g. (true && true) is (true).

(* Like families, happy system calls are all similar. It's the unhappy ones that are unhappy each in their own way.)


Then your examples:

#should result in false, and bash shows 0 as false
echo $(( 1 && 0 ))

Here, you're using && in an arithmetic context, which doesn't obey the same rules as that of exit statuses. Here, 0 is false, and 1 is true. So 1 && 0 is (true AND false), which is (false), or 0.

# should result in true,  and bash shows 1 as true
echo $(( 1 || 0 )) 

Similar to the above.

# does not show 'hi'; even though if false is zero per first example, then
# this should be considered success exit code and 'hi' to be shown!!
false && echo 'hi'

The utility called false exits with a status of 1 (or at least some non-zero value). Which, in that context, is falsy, so the right hand side of && is skipped by way of short-circuiting logic.

# does show 'hi'. It first runs the "echo 'hi'" and interprets its exit
# status of zero as success, but then false stops the 2nd hi.
echo 'hi' && false && echo ' 2nd hi'

Same thing.


# shows 'hi 2nd hi'
echo 'hi' && (( 1 || 0 )) && echo ' 2nd hi'

You used 1 || 0 here, which is true either way, and the numerical value somewhat disappears there inside the arithmetic context. Let's try these:

$ echo foo && (( 0 )) && echo bar
foo
$ echo foo && (( 1 )) && echo bar
foo
bar

Now, ((...)) is an arithmetic construct (like $((...))), and within it, 0 is falsy. Unlike $((...)), ((...)) is also a command, so has an exit status. It exits with zero (truthy), if the expression inside evaluates to non-zero (truthy); and with 1 (falsy), if the expression inside evaluates to zero (falsy). Ok, that may be confusing, but the end result is that C-like implicit comparisons against zero work inside it, and you get the same truth value out of it, when using it with the shell's conditionals.

So, while (( i-- )); do ... loops until i goes to zero.

((( foo )) is not standard, but supported by ksh/Zsh/Bash. The standard interpretation for it would be the command foo inside two nested subshells, so would probably give a "command 'foo' not found" error.)

It might also be worth pointing out that something like (( true )) && echo maybe probably doesn't print anything. In the arithmetic context, the plain word is taken as a name of a variable (recursively in many shells), so unless you have a variable true set to a non-zero value, (( true )) will be false.


(From the department of obfuscation comes the idea of running true=0; false=1; true() (exit 1); false() (exit 0);. Now what does true && (( false )) || echo maybe && echo maybe not print and why?.)

Related Question