5

stdbool.h is usually defined as:

#define false   0
#define true    1

(Sources: OpenBSD, musl, etc.)

whereas the unix program false - which just has a unsuccessful status code, is defined as:

int
main(int argc, char *argv[])
{
    return (1);
}

for completeness, the definition of the unix program true is:

int
main(int argc, char *argv[])
{
    return (0);
}

So clearly, it is the complete opposite of the value of false in stdbool.h. I have used the version from OpenBSD as the version from coreutils is more verbose

What is the historical reason to make these different?

Kusalananda
  • 333,661
uuu
  • 722

5 Answers5

6

There are multiple ways for a program to fail. For example grep fails if it doesn't find the pattern looked for, or if it can't open one of the files specified.

Presently Unix allows 1 (unsigned) byte of value (the return code) to be passed back from a child to it's parent (I am ignoring the information about which signal killed a child, if a core was generated etc which could get a few more bits back).

As there are multiple ways to fail, it makes sense for there to be multiple return codes to encode these different failures, and to have one return code to indicate success.

It follows that a (0) is a reasonable choice to indicate success, the rest (1-255) to indicate failure (and the type of failure).

It would be perfectly possible to use 17 as meaning success, and have 0 through 16 and 18 through 255 meaning failure, but for simplicity 0 was chosen for success and 1 through 255 for the failures so there is a single range. Equally simple would have been to use 255 for success and 0 through 254 for failures. However that would limit future changes to pass back more than 8 bits and keep the simplicity of a single range of failure codes.

icarus
  • 17,920
3

You are comparing apples to pears. They taste different. :-)

Arithmetic

An arithmetic 0 means false[a] both in c and the shell:

$ if ((0)); then echo true; else echo false; fi
false

Or, in a more portable test, but somewhat more convoluted:

$ echo $(( 22 == 23 ))
0

[a] Actually, what is being written is an ASCII character (0) (ASCII in most systems, but may be others) with the decimal byte value of 48 which is transparently being converted to a byte value of 0 (A c char of value 0). And is this byte value of 0 that is taken to mean false in c

That is so for most shells as the arithmetic expansion is actually a direct copy of the c arithmetic. No differences should exist there.

Exit status

But the exit status ($?) of an utility is something different:

$ echo "$(( 22 == 23 )) $? -- $((22 == 22 )) $?"
0 0 -- 1 0

Theory

I can only offer my opinion to the reason of why this is so.

In classical logic, with its intended semantics, the truth values are true (denoted by 1 or the verum ), and untrue or false (denoted by 0 or the falsum )

Propositional Logic Truth Table: We denote the value true as 1 and value false as 0.

That is just a codification of the human way of thinking that nothing/empty is closer to false.

That description got encoded in the c language

But having only one value (0) for an exit status of error seemed too constricting, the logic got reversed, and the (0) was changed to success, leaving many more codes for the several possible reasons of error. That is how it is used in the shell.

2

The origin of true = 1 and false = 0

If you want to know the historical context of why true is 1 and false is 0 then I would like to point out that boolean algebra was "invented" by George Bool in 1847. This was way before the first electronic computers. Boolean algebra was published in "THE MATHEMATICAL ANALYSIS OF LOGIC". You can read a copy on project gutenberg: http://www.gutenberg.org/files/36884/36884-pdf.pdf

I don't claim to have read it in detail, but I have skimmed enough to see that George Bool himself was using 1 as true and 0 as false.


Boole's impact on computing

Boole's algebra ("Boolean algebra") was largely ignored until it was picked up long after his death and used in computing. Even before silicon based computers, boolean algebra became useful with regard to binary electronics (as opposed to analogue). It was simple to transpose mathematical logic operators into transistor wiring (see here). And from there it was simple enough to wire together multiple logic gates to represent a number in binary and perform simple arithmetic.


With regard to "if" statements, these were derived from CPU the operation "conditional jump" where jump is known as goto in higher level languages.

For conditional jumps CPUs have to define a useful way to select jump or not jump. The two most common conditionals are "jump if zero" and "jump if negative" (see page 39 underlined here). However the interpretation of a number as positive or negative can be subjective since the same CPU operations are used for two's compliment signed integers as "unsigned" integers.

So this is all to say, the common if statement settled on 0 for false and 1 for true because:

  • a negative / positive definition would not have worked for unsigned integers
  • and 0 for true and 1 for false would have been the opposite of commonly used Boolean algebra

Why is the shell different?

The origin of the shell doing the opposite is hard to find. 0 for success certainly dates back to the 1970s and possibly the earliest versions of unix (not sure).

My own take on it is that a program can fully succeed in only one way but fail in many ways.

What does seem clear is that the true and false command were designed to fit with the shell's interpretation of using a command's exit status in conditional logic:

if first_command ; then
    second_command
fi

If the code above is intended to mean run second_command if first_command succeeds. Then a good name for a logical placeholder that always succeeds would be true.

iono
  • 97
1

You are confusing two different things:

  1. Integers as used as boolean.

    A boolean is a response to a question: "Is this variable's value the same as this other variable's value?" The answer is a false or a true. These could be encoded as 0 or 1.

  2. Integers as used as exit-status.

    An exit-status is a statement of fact: "The utility terminated after successfully doing what it was asked to do.", "The utility encountered a particular error condition and failed." The exit-status could be encoded as any integer between 0 and 127, where zero signifies success or no error.

These are similar, but only because both use integers as a way of encoding true and false (booleans), or successful and unsuccessful (exit status). Other things that use integers are the number of centimeters an athlete can jump, and the ranking associated with that athlete based on that jump, but we rarely confuse these.

In the C language, if statements act directly on the result of the arithmetic evaluation of the given expression. Since the shell has always been geared towards execution of programs and handling of job pipelines etc., an if statement in the shell is made for determining whether a utility successfully ran or not. This involves examining the exit-status of the utility that it ran.

An exit-status is zero if the utility finished successfully. You get a boolean when you ask "Is the exit-status zero?", which is what the if statement in the shell is doing (if utility; then echo ok; else not ok; fi). If the utility wasn't successful, you'll get a false back from that test, and you may then start looking at the exit-status and the utility's diagnostic output to try to figure out what went wrong.

The exit-status may encode the specific reason the utility terminated unsuccessfully, a bit like library functions and system calls in C often sets an "errno status code" that encode similar information to the caller (see man errno and, as an example, the RETURN VALUE and ERRORS sections of stat(2) with man 2 stat).

The false utility does nothing, unsuccessfully. You can see that it fails because it sets a non-zero exit status:

$ false; echo "$?"
1

This 1 is not a boolean. It's a non-zero exit-status signifying failure.

If you want to do boolean logic in the shell, use $(( ... )) to evaluate your expression, and the test utility for comparing:

if [ "$(( a == 12 ))" -ne 0 ]; then
    echo 'a is 12'
fi

In this example, the value $a is compared as an integer against 12. If $a is 12, the arithmetic expansion would expand to 1, otherwise to 0. These are boolean values.

The exit-status of [ "$(( a == 12 ))" -ne 0 ] would be zero if $a was 12, causing the echo to be executed, or non-zero if $a wasn't 12, causing the echo to be skipped.

For utilities with more interesting exit-statuses than just 0 and 1, see e.g. the curl manual (the EXIT CODES section towards the end), and the rsync manual (the EXIT VALUES section towards the end).

Kusalananda
  • 333,661
0

I can only think of this rationale and it seems quite strong to me.

The easiest way to translate boolean operations into computer arithmetic is to use 0 for FALSE and 1 for TRUE, and then all the operations with them make perfect sense, i.e.

TRUE  && TRUE =  TRUE  ( 1 * 1 = 1)
TRUE  && FALSE = FALSE ( 1 * 0 = 0)
TRUE  || FALSE = TRUE  ( 1 + 0 = 1)
FALSE || FALSE = FALSE ( 0 + 0 = 0)

And so on and so forth.

At the same time programs need multiple exit statuses to report various errors. It's logical to make the correct exit status as 0, and then use the remaning 255 (unsigned char) as bad exit statuses.

Now of course, 1 could have been made the good exit status but then the bad ones would have become a torn array of 0 and 2..555 which is very illogical.

  • 1
    See my answer for a possible lead on the real reason, though it is incomplete :/ Yours seems much more logical than my previous suggestion though! – Nordine Lotfi Mar 25 '21 at 19:19
  • 1
    c programs also have different exit values, that's the reason in C development, the real error is usually int the global variable errno. – uuu Mar 25 '21 at 19:37
  • 2
    With active-low logic, you'd just swap the names of "AND" and "OR", right? Or otherwise build the gates to support that. And again, it would make perfect sense. – ilkkachu Mar 25 '21 at 19:44
  • Don't understand why I'm being downvoted despite being the first to give an answer which is repeated below. – Artem S. Tashkinov Mar 27 '21 at 19:05
  • I think your truth table is very misleading because it misses the fact that 1+1=2 so "or" cannot be correctly represented as "+". And as Ilkkachu points out, if that logic works then it works equally well if true is 0. In any case, CPU arithmetic is derived from Boolean logic, not the other way around. I don't see this first part of your answer on other answers so I wonder if that caused the down-vote? – Philip Couling Mar 29 '21 at 00:05