7

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.

shaheen g
  • 183
  • 3
    Related if not a dupe: https://unix.stackexchange.com/questions/429039 Also note that there is a difference between acting on an exit status (0 is true) and doing an arithmetic evaluation (0 is false) – Kusalananda Feb 04 '21 at 18:08
  • 1
    there is nothing to inverse. you just missed the meaning of return codes https://unix.stackexchange.com/q/110348 – alecxs Feb 04 '21 at 19:17
  • 2
    You're mixing up the exit status of a command, with its output (what it's writing to stdout). You're also mixing up the arithmetic evaluation within $((...)) or ((...)) (a mini-language with its own different rules, and where a 0 number is "false", && and || have different precedences, etc) with the command line evaluation (where a status of 0 is true, any other status is false, and && and || have the same precedence). There's no paradox -- you're simply mixing things up. In ways which are puzzling -- how could someone be so familiar and confused at the same time? ;-) –  Feb 04 '21 at 19:44
  • Thanks @UncleBilly! I did not know true and false are also programs with exit code of 0 and 1! That's it! Now things are crystal clear and I am back to normal thinking process! ;) – shaheen g Feb 05 '21 at 19:18

3 Answers3

10

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?.)

ilkkachu
  • 138,973
  • Saying that true and false are also programs with exit code of 0 and 1, respectively, did the click for me. Thanks for going through each example with detailed explanation. – shaheen g Feb 05 '21 at 19:04
  • 1
    @shaheeng, indeed. They're a bit odd, since they're not really truth values, you can't compare against them. So, perhaps mostly useful for placeholders or testing. You could do something like foo=true; if "$foo"; then ..., but I would be careful with that (i.e. use something like if [ "$foo" = true ]; then instead.) – ilkkachu Feb 05 '21 at 20:26
4

Yes, command exit status inverts the usual mapping between integers and true/false concepts.

The reason for this is that commands can fail in many different ways. There's only one zero value, but many non-zero values (255 in this case, since only 8 bits of the exit status are used). By using non-zero as the failure representation, you can use different non-zero values to encode different types of failure.

For example, grep uses exit status 1 when it successfully reads the files but doesn't find any matches, but status 2 when it gets an actual error.

If your application doesn't care about the distinction between failure modes, you can treat them all as false by using if or the logical operators. If you care, you compare $? explicitly.

This method of assigning different meanings to different exit statuses isn't very common, but it's useful to have it available when you need it. Most programs simply use 1 as their failure code if they have no need to distinguish.

true
echo $? # 0
false
echo $? # 1
Barmar
  • 9,927
  • So, I used the echo $? after passing running true and false on bash command line and as @ilkkachu said, the exit codes are 0 and 1, respectively. true; echo $?; false; echo $? Thanks @Barmar for the tip! – shaheen g Feb 05 '21 at 19:11
1

The behaviour depends on the shell. Assuming you are using Bash, the answer comes from the Bash manual:

Lists

...

AND and OR lists are sequences of one or more pipelines separated by the && and || control operators, respectively. AND and OR lists are executed with left associativity. An AND list has the form

          command1 && command2

command2 is executed if, and only if, command1 returns an exit status of zero (success).

An OR list has the form

          command1 || command2

command2 is executed if, and only if, command1 returns a non-zero exit status. The return status of AND and OR lists is the exit status of the last command executed in the list.

cryptarch
  • 1,270
  • 3
    This is true for all POSIX-like shells, not just bash. – Kusalananda Feb 04 '21 at 18:34
  • Bash's POSIX compliance is a bit half-hearted afaik. Since the opening post is tagged Bash, Bash is what we're talking about, not POSIX. – cryptarch Feb 04 '21 at 18:40
  • 1
    Sure, it's just that all POSIX shells behave this way, not just bash. See the standard here, for example: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_03 Also, you may want to explain the bit that they find puzzling, how zero sometimes seems to be true and other times seems to be false. – Kusalananda Feb 04 '21 at 18:46
  • 3
    @Kusalananda, it's probably true for all shells, period, since otherwise it would contradict the way all programs external to the shell use their exit status. – ilkkachu Feb 04 '21 at 19:49
  • 1
    @cryptarch, Bash is POSIX-like enough in the basic stuff like this. It's not like it's csh, fish or Zsh. – ilkkachu Feb 04 '21 at 19:50
  • .. && ((1 || 0)) && ... is not standard shell syntax, and that exactly seems to be the "paradox" the OP is trying to latch onto. –  Feb 04 '21 at 19:53
  • "it's probably true for all shells, period" You've obviously never written a shell ;) You can do it however you want when you're programming from scratch. You're really talking about shells which follow the POSIX model, which everyone has already been quick to assert is what should really be discussed here instead of Bash-as-some-arbitrary-shell. – cryptarch Feb 04 '21 at 20:10
  • 4
    @cryptarch, no, I'm talking about all shells that have any common use on Unix-like systems. Including stuff like fish, which doesn't even try to be remotely POSIX-like. Yet it treats an exit status of zero as truthy, so works with all the usual tools, e.g. /bin/true; and echo moi prints moi, and if grep -q root /etc/passwd; echo found it; end prints "found it". – ilkkachu Feb 04 '21 at 20:24
  • 5
    @cryptarch Of course if one implements a shell of their own, they can do whatever they like, even decide that prime numbers are true, and non-primes false. It just won't be very useful with all the existing utilities that return a zero on success. Though if we go further, and implement a whole new operating system, then the utilities have to change too. (If anyone bothers to port them.) No idea if Windows had something similar to exit codes, but I think DOS used the idea that 0 is success and > 0 an error, maybe? – ilkkachu Feb 04 '21 at 20:30
  • 1
    @cryptarch, if it helps, imagine I wrote "for almost all shells", or "all common shells". (I won't write "all sane shells", since I'm not sure POSIX-like shells are totally sane.) – ilkkachu Feb 04 '21 at 20:32
  • 1
    @ikkachu haha, very good, thanks for clarifying your position :) – cryptarch Feb 04 '21 at 20:35