So between 2^63 and 2^64-1, you get negative integers showing you how far off from ULONG_MAX you are.
No. How do you figure that? By your own example, the max is:
> max=$((2**63 - 1)); echo $max
9223372036854775807
If "overflow" meant "you get negative integers showing you how far off from ULONG_MAX you are", then if we add one to that, shouldn't we get -1? But instead:
> echo $(($max + 1))
-9223372036854775808
Perhaps you mean this is a number you can add to $max
to get a negative difference, since:
> echo $(($max + 1 + $max))
-1
But this does not in fact continue to hold true:
> echo $(($max + 2 + $max))
0
This is because the system uses two's complement to implement signed integers.1 The value resulting from an overflow is NOT an attempt to provide you with a difference, a negative difference, etc. It is literally the result of truncating a value to a limited number of bits, then having it interpreted as a two's complement signed integer. For example, the reason $(($max + 1 + $max))
comes out as -1 is because the highest value in two's complement is all bits set except the highest bit (which indicates negative); adding these together basically means carrying all the bits to the left so you end up with (if the size were 16-bits, and not 64):
11111111 11111110
The high (sign) bit is now set because it carried over in the addition. If you add one more (00000000 00000001) to that, you then have all bits set, which in two's complement is -1.
I think that partially answers the second half of your first question -- "Why are the negative integers...exposed to the end user?". First, because that is the correct value according to the rules of 64-bit two's complement numbers. This is the conventional practice of most (other) general purpose high level programming languages (I cannot think of one that does not do this), so bash
is adhering to convention. Which is also the answer to the first part of the first question -- "What's the rationale?": this is the norm in the specification of programming languages.
WRT the 2nd question, I have not heard of systems which interactively change ULONG_MAX.
If someone arbitrarily changes the value of the unsigned integer maximum in limits.h, then recompiles bash, what can we expect will happen?
It would not make any difference to how the arithmetic comes out, because this is not an arbitrary value that is used to configure the system -- it's a convenience value that stores an immutable constant reflecting the hardware. By analogy, you could redefine c to be 55 mph, but the speed of light will still be 186,000 miles per second. c is not a number used to configure the universe -- it's a deduction about the nature of the universe.
ULONG_MAX is exactly the same. It is deduced/calculated based on the nature of N-bit numbers. Changing it in limits.h
would be a very bad idea if that constant is used somewhere assuming it is supposed to represent the reality of the system.
And you cannot change the reality imposed by your hardware.
1. I don't think that this (the means of integer representation) is actually guaranteed by bash
, since it depends on the underlying C library and standard C does not guarantee that. However, this is what is used on most normal modern computers.
The <
isn't directly an aspect of bash command substitution. It is a redirection operator (like a pipe), which some shells allow without a command (POSIX does not specify this behavior).
Perhaps it would be more clear with more spaces:
echo $( < $FILE )
this is effectively* the same as the more POSIX-safe
echo $( cat $FILE )
... which is also effectively*
echo $( cat < $FILE )
Let's start with that last version. This runs cat
with no arguments, which means it will read from standard input. $FILE
is redirected into standard input due to the <
, so cat
puts its contents are put into standard output. The $(command)
subsitution then pushes cat
's output into arguments for echo
.
In bash
(but not in the POSIX standard), you can use <
without a command. bash
(and zsh
and ksh
but not dash
) will interpret that as if cat <
, though without invoking a new subprocess. As this is native to the shell, it is faster than literally running the external command cat
. *This is why I say "effectively the same as."
Best Answer
In many computer languages, operators with the same precedence are left-associative. That is, in the absence of grouping structures, leftmost operations are executed first. Bash is no exception to this rule.
This is important because, in Bash,
&&
and||
have the same precedence.So what happens in your example is that the leftmost operation (
||
) is carried out first:Since
true
is obviously true, the||
operator short-circuits and the whole statement is considered true without the need to evaluateecho aaa
as you would expect. Now it remains to do the rightmost operation:Since the first operation evaluated to true (i.e. had a 0 exit status), it's as if you're executing
so the
&&
will not short-circuit, which is why you seebbb
echoed.You would get the same behavior with
Notes based on the comments
[[...]]
or((...))
or use the-o
and-a
operators as arguments to thetest
or[
commands. In such cases, AND (&&
or-a
) takes precedence over OR (||
or-o
). Thanks to Stephane Chazelas' comment for clarifying this point.It seems that in C and C-like languages
&&
has higher precedence than||
which is probably why you expected your original construct to behave likeThis is not the case with Bash, however, in which both operators have the same precedence, which is why Bash parses your expression using the left-associativity rule. Thanks to Kevin's comment for bringing this up.
There might also be cases where all 3 expressions are evaluated. If the first command returns a non-zero exit status, the
||
won't short circuit and goes on to execute the second command. If the second command returns with a zero exit status, then the&&
won't short-circuit as well and the third command will be executed. Thanks to Ignacio Vazquez-Abrams' comment for bringing this up.