Bash Shell Scripting/Shell Arithmetic
Arithmetic expressions in Bash are closely modeled on those in C, so they are very similar to those in other C-derived languages, such as C++, Java, Perl, JavaScript, C#, and PHP. One major difference is that Bash only supports integer arithmetic (whole numbers), not floating-point arithmetic (decimals and fractions); something like 3 + 4
means what you'd expect (7), but something like 3.4 + 4.5
is a syntax error. Something like 13 / 5
is fine, but performs integer division, so evaluates to 2 rather than to 2.6.
Arithmetic expansion
editPerhaps the most common way to use arithmetic expressions is in arithmetic expansion, where the result of an arithmetic expression is used as an argument to a command. Arithmetic expansion is denoted $(( … ))
. For example, this command:
echo $(( 3 + 4 * (5 - 1) ))
prints 19
.
expr (deprecated)
editAnother way to use arithmetic expressions is using the Unix program "expr", which was popular before Bash supported math.[1] Similar to Arithmetic expansion, this command:
echo `expr 3 + 4 \* \( 5 - 1 \)`
prints 19
. Note that using "expr" requires an escape character "\" before the multiplication operator "*" and parentheses. Further note the spaces between each operator symbol, including the parentheses.
Numeric operators
editIn addition to the familiar notations +
(addition) and -
(subtraction), arithmetic expressions also support *
(multiplication), /
(integer division, described above), %
(modulo division, the "remainder" operation; for example, 11 divided by 5 is 2 remainder 1, so 11 % 5
is 1
), and **
("exponentiation", i.e. involution; for example, 24 = 16, so 2 ** 4
is 16
).
The operators +
and -
, in addition to their "binary" (two-operand) senses of "addition" and "subtraction", have "unary" (one-operand) senses of "positive" and "negative". Unary +
has basically no effect; unary -
inverts the sign of its operand. For example, -(3*4)
evaluates to -12
, and -(-(3*4))
evaluates to 12
.
Referring to variables
editInside an arithmetic expression, shell variables can be referred to directly, without using variable expansion (that is, without the dollar sign $
). For example, this:
i=2+3
echo $(( 7 * i ))
prints 35
. (Note that i
is evaluated first, producing 5, and then it's multiplied by 7. If we had written $i
rather than i
, mere string substitution would have been performed; 7 * 2+3
equals 14 + 3, that is, 17 — probably not what we want.)
The previous example shown using "expr":
i=`expr 2 + 3`
echo `expr 7 \* $i`
prints 35
.
Assigning to variables
editShell variables can also be assigned to within an arithmetic expression. The notation for this is similar to that of regular variable assignment, but is much more flexible. For example, the previous example could be rewritten like this:
echo $(( 7 * (i = 2 + 3) ))
except that this sets $i
to 5
rather than to 2+3
. Note that, although arithmetic expansion looks a bit like command substitution, it is not executed in a subshell; this command actually sets $i
to 5
, and later commands can use the new value. (The parentheses inside the arithmetic expression are just the normal mathematical use of parentheses to control the order of operations.)
In addition to the simple assignment operator =
, Bash also supports compound operators such as +=
, -=
, *=
, /=
, and %=
, which perform an operation followed by an assignment. For example, (( i *= 2 + 3 ))
is equivalent to (( i = i * (2 + 3) ))
. In each case, the expression as a whole evaluates to the new value of the variable; for example, if $i
is 4
, then (( j = i *= 3 ))
sets both $i
and $j
to 12
.
Lastly, Bash supports increment and decrement operators. The increment operator ++
increases a variable's value by 1; if it precedes the variable-name (as the "pre-increment" operator), then the expression evaluates to the variable's new value, and if it follows the variable-name (as the "post-increment" operator), then the expression evaluates to the variable's old value. For example, if $i
is 4
, then (( j = ++i ))
sets both $i
and $j
to 5
, while (( j = i++ ))
sets $i
to 5
and $j
to 4
. The decrement operator --
is exactly the same, except that it decreases the variable's value by 1. Pre-decrement and post-decrement are completely analogous to pre-increment and post-increment.
Arithmetic expressions as their own commands
editA command can consist entirely of an arithmetic expression, using either of the following syntaxes:
(( i = 2 + 3 ))
let 'i = 2 + 3'
Either of these commands will set $i
to 5
. Both styles of command return an exit status of zero ("successful" or "true") if the expression evaluates to a non-zero value, and an exit status of one ("failure" or "false") if the expression evaluates to zero. For example, this:
(( 0 )) || echo zero
(( 1 )) && echo non-zero
will print this:
zero non-zero
The reason for this counterintuitive behavior is that in C, zero means "false" and non-zero values (especially one) mean "true". Bash maintains that legacy inside arithmetic expressions, then translates it into the usual Bash convention at the end.
The comma operator
editArithmetic expressions can contain multiple sub-expressions separated by commas ,
. The result of the last sub-expression becomes the overall value of the full expression. For example, this:
echo $(( i = 2 , j = 2 + i , i * j ))
sets $i
to 2
, sets $j
to 4
, and prints 8
.
The let
built-in actually supports multiple expressions directly without needing a comma; therefore, the following three commands are equivalent:
(( i = 2 , j = 2 + i , i * j ))
let 'i = 2 , j = 2 + i , i * j'
let 'i = 2' 'j = 2 + i' 'i * j'
Comparison, Boolean, and conditional operators
editArithmetic expressions support the integer comparison operators <
, >
, <=
(meaning ≤), >=
(meaning ≥), ==
(meaning =), and !=
(meaning ≠). Each evaluates to 1
for "true" or 0
for "false".
They also support the Boolean operators &&
("and"), which evaluates to 0
if either of its operands is zero, and to 1
otherwise; ||
("or"), which evaluates to 1
if either of its operands is nonzero, and to 0
otherwise; and !
("not"), which evaluates to 1
if its operand is zero, and to 0
otherwise. Aside from their use of zero to mean "false" and nonzero values to mean "true", these are just like the operators &&
, ||
, and !
that we've seen outside arithmetic expressions. Like those operators, these are "short-cutting" operators that do not evaluate their second argument if their first argument is enough to determine a result. For example, (( ( i = 0 ) && ( j = 2 ) ))
will not evaluate the ( j = 2 )
part, and therefore will not set $j
to 2
, because the left operand of &&
is zero ("false").
And they support the conditional operator b ? e1 : e2
. This operator evaluates e1
, and returns its result, if b
is nonzero; otherwise, it evaluates e2
and returns its result.
These operators can be combined in complex ways:
(( i = ( ( a > b && c < d + e || f == g + h ) ? j : k ) ))
Arithmetic for-loops
editAbove, we saw one style of for-loop, that looked like this:
# print all integers 1 through 20:
for i in {1..20} ; do
echo $i
done
Bash also supports another style, modeled on the for-loops of C and related languages, using shell arithmetic:
# print all integers 1 through 20:
for (( i = 1 ; i <= 20 ; ++i )) ; do
echo $i
done
This for-loop uses three separate arithmetic expressions, separated by semicolons ;
(and not commas ,
— these are completely separate expressions, not just sub-expressions). The first is an initialization expression, run before the loop begins. The second is a test expression; it is evaluated before every potential loop iteration (including the first), and if it evaluates to zero ("false"), then the loop exits. The third is a counting expression; it is evaluated at the end of each loop iteration. In other words, this for-loop is exactly equivalent to this while-loop:
# print all integers 1 through 20:
(( i = 1 ))
while (( i <= 20 )) ; do
echo $i
(( ++i ))
done
but, once you get used to the syntax, it makes it more clear what is going on.
Bitwise operators
editIn addition to regular arithmetic and Boolean operators, Bash also offers "bitwise" operators, meaning operators that operate on integers qua bit-strings rather than qua integers. If you are not already familiar with this concept, you can safely ignore these.
Just as in C, the bitwise operators are &
(bitwise "and"), |
(bitwise "or"), ^
(bitwise "exclusive or"), ~
(bitwise "not"), <<
(bitwise left-shift), and >>
(bitwise right-shift), as well as &=
and |=
and ^=
(which include assignment, just like +=
).
Integer literals
editAn integer constant is expressed as an integer literal. We have already seen many of these; 34
, for example, is an integer literal denoting the number 34. All of our examples have been decimal (base ten) integer literals, which is the default; but in fact, literals may be expressed in any base in the range 2–64, using the notation base#value
(with the base itself being expressed in base-ten). For example, this:
echo $(( 12 )) # use the default of base ten (decimal) echo $(( 10#12 )) # explicitly specify base ten (decimal) echo $(( 2#1100 )) # base two (binary) echo $(( 8#14 )) # base eight (octal) echo $(( 16#C )) # base sixteen (hexadecimal) echo $(( 8 + 2#100 )) # eight in base ten (decimal), plus four in base two (binary)
will print 12
six times. (Note that this notation only affects how an integer literal is interpreted. The result of the arithmetic expansion is still expressed in base ten, regardless.)
For bases 11 through 36, the English letters A through Z are used for digit-values 10 through 35. This is not case-sensitive. For bases 37 through 64, however, it is specifically the lowercase English letters that are used for digit-values 10 through 35, with the uppercase letters being used for digit-values 36 through 61, the at-sign @
being used for digit-value 62, and the underscore _
being used for digit-value 63. For example, 64#@A3
denotes 256259 (62 × 642 + 36 × 64 + 3).
There are also two special notations: prefixing a literal with 0
indicates base-eight (octal), and prefixing it with 0x
or 0X
indicates base-sixteen (hexadecimal). For example, 030
is equivalent to 8#30
, and 0x6F
is equivalent to 16#6F
.
Integer variables
editA variable may be declared as an integer variable — that is, its "integer attribute" may be "set" — by using this syntax:
declare -i n
After running the above command, any subsequent assignments to n
will automatically cause the right-hand side to be interpreted as an arithmetic expression. For example, this:
declare -i n
n='2 + 3 > 4'
is more or less equivalent to this:
n=$((2 + 3 > 4))
except that the first version's declare -i n
will continue to affect later assignments as well.
In the first version, note the use of quotes around the right-hand side of the assignment. Had we written n=2 + 3 > 4
, it would have meant "run the command +
with the argument 3
, passing in the environment variable n
set to 2
, and redirecting standard output into the file 4
"; which is to say, setting a variable's integer attribute doesn't affect the overall parsing of assignment statements, but merely controls the interpretation of the value that is finally assigned to the variable.
We can "unset" a variable's integer attribute, turning off this behavior, by using the opposite command:
declare +i n
The declare
built-in command has a number of other uses as well: there are a few other attributes a variable can have, and declare
has a few other features besides turning attributes on and off. In addition, a few of its properties bear note:
- As with
local
andexport
, the argument can be a variable assignment; for example,declare -i n=2+3
sets$n
's integer attribute and sets it to5
. - As with
local
andexport
, multiple variables (and/or assignments) can be specified at once; for example,declare -i m n
sets both$m
's integer attribute and$n
's. - When used inside a function,
declare
implicitly localizes the variable (unless the variable is already local), which also has the effect of locally unsetting it (unless the assignment syntax is used).
Non-integer arithmetic
editAs mentioned above, Bash shell arithmetic only supports integer arithmetic. However, external programs can often be used to obtain similar functionality for non-integer values. In particular, the common Unix utility bc
is often used for this. The following command:
echo "$(echo '3.4 + 2.2' | bc)"
prints 5.6
. Needless to say, since bc
is not so tightly integrated with Bash as shell arithmetic is, it is not as convenient; for example, something like this:
# print the powers of two, from 1 to 512:
for (( i = 1 ; i < 1000 ; i *= 2 )) ; do
echo $i
done
would, to support non-integers, become something like this:
# print the powers of one-half, from 1 to 1/512:
i=1
while [ $( echo "$i > 0.001" | bc ) = 1 ] ; do
echo $i
i=$( echo "scale = scale($i) + 1 ; $i / 2" | bc )
done
Part of this is because we can no longer use an arithmetic for-loop; part of it is because referring to variables and assigning to variables is trickier now (since bc
is not aware of the shell's variables, only its own, unrelated ones); and part of it is because bc
communicates with the shell only via input and output.