| Chapter 8. Operations and Related Topics
8.1. Operatorsassignment - variable assignment
Initializing or changing the value of a variable - =
All-purpose assignment operator, which works for both arithmetic and string assignments. var=27category=minerals # No spaces allowed after the "=". | | Do not confuse the "=" assignment operator with the = test operator. # = as a test operatorif [ "$string1" = "$string2" ]then commandfi# if [ "X$string1" = "X$string2" ] is safer,#+ to prevent an error message should one of the variables be empty.# (The prepended "X" characters cancel out.) | |
arithmetic operators - +
plus - -
minus - *
multiplication - /
division - **
exponentiation # Bash, version 2.02, introduced the "**" exponentiation operator.let "z=5**3" # 5 * 5 * 5echo "z = $z" # z = 125 | - %
modulo, or mod (returns the remainder of an integer division operation) 5/3 = 1, with remainder 2 This operator finds use in, among other things, generating numbers within a specific range (see Example 9-11 and Example 9-15) and formatting program output (see Example 27-16 and Example A-6). It can even be used to generate prime numbers, (see Example A-15). Modulo turns up surprisingly often in numerical recipes. Example 8-1. Greatest common divisor #!/bin/bash# gcd.sh: greatest common divisor# Uses Euclid's algorithm# The "greatest common divisor" (gcd) of two integers#+ is the largest integer that will divide both, leaving no remainder.# Euclid's algorithm uses successive division.# In each pass,#+ dividend <--- divisor#+ divisor <--- remainder#+ until remainder = 0.# The gcd = dividend, on the final pass.## For an excellent discussion of Euclid's algorithm, see#+ Jim Loy's site, http://www.jimloy.com/number/euclids.htm.# ------------------------------------------------------# Argument checkARGS=2E_BADARGS=85if [ $# -ne "$ARGS" ]then echo "Usage: `basename $0` first-number second-number" exit $E_BADARGSfi# ------------------------------------------------------gcd (){ dividend=$1 # Arbitrary assignment. divisor=$2 #! It doesn't matter which of the two is larger. # Why not? remainder=1 # If an uninitialized variable is used inside #+ test brackets, an error message results. until [ "$remainder" -eq 0 ] do # ^^^^^^^^^^ Must be previously initialized! let "remainder = $dividend % $divisor" dividend=$divisor # Now repeat with 2 smallest numbers. divisor=$remainder done # Euclid's algorithm} # Last $dividend is the gcd.gcd $1 $2echo; echo "GCD of $1 and $2 = $dividend"; echo# Exercises :# ---------# 1) Check command-line arguments to make sure they are integers,#+ and exit the script with an appropriate error message if not.# 2) Rewrite the gcd () function to use local variables.exit 0 | - +=
plus-equal (increment variable by a constant) let "var += 5" results in var being incremented by 5. - -=
minus-equal (decrement variable by a constant) - *=
times-equal (multiply variable by a constant) let "var *= 4" results in var being multiplied by 4. - /=
slash-equal (divide variable by a constant) - %=
mod-equal (remainder of dividing variable by a constant) Arithmetic operators often occur in an expr or let expression. Example 8-2. Using Arithmetic Operations #!/bin/bash# Counting to 11 in 10 different ways.n=1; echo -n "$n " let "n = $n + 1" # let "n = n + 1" also works.echo -n "$n " : $((n = $n + 1))# ":" necessary because otherwise Bash attempts#+ to interpret "$((n = $n + 1))" as a command.echo -n "$n " (( n = n + 1 ))# A simpler alternative to the method above.# Thanks, David Lombard, for pointing this out.echo -n "$n " n=$(($n + 1))echo -n "$n " : $[ n = $n + 1 ]# ":" necessary because otherwise Bash attempts#+ to interpret "$[ n = $n + 1 ]" as a command.# Works even if "n" was initialized as a string.echo -n "$n " n=$[ $n + 1 ]# Works even if "n" was initialized as a string.#* Avoid this type of construct, since it is obsolete and nonportable.# Thanks, Stephane Chazelas.echo -n "$n " # Now for C-style increment operators.# Thanks, Frank Wang, for pointing this out.let "n++" # let "++n" also works.echo -n "$n " (( n++ )) # (( ++n )) also works.echo -n "$n " : $(( n++ )) # : $(( ++n )) also works.echo -n "$n " : $[ n++ ] # : $[ ++n ] also worksecho -n "$n " echoexit 0 |
| Integer variables in older versions of Bash were signedlong (32-bit) integers, in the range of-2147483648 to 2147483647. An operation that took a variableoutside these limits gave an erroneous result. echo $BASH_VERSION # 1.14a=2147483646echo "a = $a" # a = 2147483646let "a+=1" # Increment "a".echo "a = $a" # a = 2147483647let "a+=1" # increment "a" again, past the limit.echo "a = $a" # a = -2147483648 # ERROR: out of range, # + and the leftmost bit, the sign bit, # + has been set, making the result negative. | As of version >= 2.05b, Bash supports 64-bit integers. | |
Bash does not understand floating point arithmetic. It treats numbers containing a decimal point as strings. a=1.5let "b = $a + 1.3" # Error.# t2.sh: let: b = 1.5 + 1.3: syntax error in expression# (error token is ".5 + 1.3")echo "b = $b" # b=1 | Use bc in scripts that that need floatingpoint calculations or math library functions. | bitwise operators - <<
bitwise left shift (multiplies by 2 for each shift position) - <<=
left-shift-equal let "var <<= 2" results in var left-shifted 2 bits (multiplied by 4) - >>
bitwise right shift (divides by 2 for each shift position) - >>=
right-shift-equal (inverse of <<=) - &
bitwise AND - &=
bitwise AND-equal - |
bitwise OR - |=
bitwise OR-equal - ~
bitwise NOT - ^
bitwise XOR - ^=
bitwise XOR-equal
logical (boolean) operators - !
NOT if [ ! -f $FILENAME ]then ... | - &&
AND if [ $condition1 ] && [ $condition2 ]# Same as: if [ $condition1 -a $condition2 ]# Returns true if both condition1 and condition2 hold true...if [[ $condition1 && $condition2 ]] # Also works.# Note that && operator not permitted inside brackets#+ of [ ... ] construct. | | && may also be used, depending on context, in an and list to concatenate commands. | - ||
OR if [ $condition1 ] || [ $condition2 ]# Same as: if [ $condition1 -o $condition2 ]# Returns true if either condition1 or condition2 holds true...if [[ $condition1 || $condition2 ]] # Also works.# Note that || operator not permitted inside brackets#+ of a [ ... ] construct. | | Bash tests the exit status of each statement linked with a logical operator. | Example 8-3. Compound Condition Tests Using && and || #!/bin/basha=24b=47if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]then echo "Test #1 succeeds." else echo "Test #1 fails." fi# ERROR: if [ "$a" -eq 24 && "$b" -eq 47 ]#+ attempts to execute ' [ "$a" -eq 24 '#+ and fails to finding matching ']'.## Note: if [[ $a -eq 24 && $b -eq 24 ]] works.# The double-bracket if-test is more flexible#+ than the single-bracket version. # (The "&&" has a different meaning in line 17 than in line 6.)# Thanks, Stephane Chazelas, for pointing this out.if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]then echo "Test #2 succeeds." else echo "Test #2 fails." fi# The -a and -o options provide#+ an alternative compound condition test.# Thanks to Patrick Callahan for pointing this out.if [ "$a" -eq 24 -a "$b" -eq 47 ]then echo "Test #3 succeeds." else echo "Test #3 fails." fiif [ "$a" -eq 98 -o "$b" -eq 47 ]then echo "Test #4 succeeds." else echo "Test #4 fails." fia=rhinob=crocodileif [ "$a" = rhino ] && [ "$b" = crocodile ]then echo "Test #5 succeeds." else echo "Test #5 fails." fiexit 0 | The && and || operators also find use in an arithmetic context. bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))1 0 1 0 |
miscellaneous operators - ,
Comma operator The comma operator chains together two or more arithmetic operations. All the operations are evaluated (with possible side effects. let "t1 = ((5 + 3, 7 - 1, 15 - 4))" echo "t1 = $t1" ^^^^^^ # t1 = 11# Here t1 is set to the result of the last operation. Why?let "t2 = ((a = 9, 15 / 3))" # Set "a" and calculate "t2".echo "t2 = $t2 a = $a" # t2 = 5 a = 9 | The comma operator finds use mainly in for loops. See Example 11-12.
8.2. Numerical ConstantsA shell script interprets a numberas decimal (base 10), unless that number has aspecial prefix or notation. A number preceded by a0 is octal(base 8). A number preceded by 0xis hexadecimal (base 16). A numberwith an embedded # evaluates asBASE#NUMBER (with range and notationalrestrictions). Example 8-4. Representation of numerical constants #!/bin/bash# numbers.sh: Representation of numbers in different bases.# Decimal: the defaultlet "dec = 32" echo "decimal number = $dec" # 32# Nothing out of the ordinary here.# Octal: numbers preceded by '0' (zero)let "oct = 032" echo "octal number = $oct" # 26# Expresses result in decimal.# --------- ------ -- -------# Hexadecimal: numbers preceded by '0x' or '0X'let "hex = 0x32" echo "hexadecimal number = $hex" # 50echo $((0x9abc)) # 39612# ^^ ^^ double-parentheses arithmetic expansion/evaluation# Expresses result in decimal.# Other bases: BASE#NUMBER# BASE between 2 and 64.# NUMBER must use symbols within the BASE range, see below.let "bin = 2#111100111001101" echo "binary number = $bin" # 31181let "b32 = 32#77" echo "base-32 number = $b32" # 231let "b64 = 64#@_" echo "base-64 number = $b64" # 4031# This notation only works for a limited range (2 - 64) of ASCII characters.# 10 digits + 26 lowercase characters + 26 uppercase characters + @ + _echoecho $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA)) # 1295 170 44822 3375# Important note:# --------------# Using a digit out of range of the specified base notation#+ gives an error message.let "bad_oct = 081" # (Partial) error message output:# bad_oct = 081: value too great for base (error token is "081")# Octal numbers use only digits in the range 0 - 7.exit $? # Exit value = 1 (error)# Thanks, Rich Bartell and Stephane Chazelas, for clarification. |
8.3. The Double-Parentheses Construct
Similar to the let command, the (( ... )) construct permits arithmetic expansion and evaluation. In its simplest form, a=$(( 5 + 3 )) would set a to 5 + 3, or 8. However, this double-parentheses construct is also a mechanism for allowing C-style manipulation of variables in Bash, for example, (( var++ )).
Example 8-5. C-style manipulation of variables #!/bin/bash# c-vars.sh# Manipulating a variable, C-style, using the (( ... )) construct.echo(( a = 23 )) # Setting a value, C-style, #+ with spaces on both sides of the "=".echo "a (initial value) = $a" # 23(( a++ )) # Post-increment 'a', C-style.echo "a (after a++) = $a" # 24(( a-- )) # Post-decrement 'a', C-style.echo "a (after a--) = $a" # 23(( ++a )) # Pre-increment 'a', C-style.echo "a (after ++a) = $a" # 24(( --a )) # Pre-decrement 'a', C-style.echo "a (after --a) = $a" # 23echo######################################################### Note that, as in C, pre- and post-decrement operators#+ have different side-effects.n=1; let --n && echo "True" || echo "False" # Falsen=1; let n-- && echo "True" || echo "False" # True# Thanks, Jeroen Domburg.########################################################echo(( t = a<45?7:11 )) # C-style trinary operator.# ^ ^ ^echo "If a < 45, then t = 7, else t = 11." # a = 23echo "t = $t " # t = 7echo# -----------------# Easter Egg alert!# -----------------# Chet Ramey seems to have snuck a bunch of undocumented C-style#+ constructs into Bash (actually adapted from ksh, pretty much).# In the Bash docs, Ramey calls (( ... )) shell arithmetic,#+ but it goes far beyond that.# Sorry, Chet, the secret is out.# See also "for" and "while" loops using the (( ... )) construct.# These work only with version 2.04 or later of Bash.exit | See also Example 11-12 and Example 8-4.
8.4. Operator Precedence
In a script, operations execute in order of precedence: the higher precedence operationsexecute before the lower precedence ones. Table 8-1. Operator Precedence Operator | Meaning | Comments | | | HIGHEST PRECEDENCE | var++ var-- | post-increment, post-decrement | C-style operators | ++var --var | pre-increment, pre-decrement | | | | | ! ~ | negation | logical / bitwise, inverts sense of following operator | | | | ** | exponentiation | arithmetic operation | * / % | multiplication, division, modulo | arithmetic operation | + - | addition, subtraction | arithmetic operation | | | | << >> | left, right shift | bitwise | | | | -z -n | unary comparison | string is/is-not null | -e -f -t -x, etc. | unary comparison | file-test | < -lt > -gt <= -le >= -ge | compound comparison | string and integer | -nt -ot -ef | compound comparison | file-test | == -eq != -ne | equality / inequality | test operators, string and integer | | | | & | AND | bitwise | ^ | XOR | exclusive OR, bitwise | | | OR | bitwise | | | | && -a | AND | logical, compound comparison | || -o | OR | logical, compound comparison | | | | ?: | trinary operator | C-style | = | assignment | (do not confuse with equality test) | *= /= %= += -= <<= >>= &= | combination assignment | times-equal, divide-equal, mod-equal, etc. | | | | , | comma | links a sequence of operations | | | LOWEST PRECEDENCE | In practice, all you really need to remember is the following: The "My Dear Aunt Sally" mantra (multiply, divide, add, subtract) for the familiar arithmetic operations. The compound logical operators, &&, ||, -a, and -o have low precedence. The order of evaluation of equal-precedence operators is usually left-to-right.
Now, let's utilize our knowledge of operator precedence to analyze a couple of lines from the /etc/init.d/functions file, as found in the Fedora Core Linux distro. while [ -n "$remaining" -a "$retry" -gt 0 ]; do# This looks rather daunting at first glance.# Separate the conditions:while [ -n "$remaining" -a "$retry" -gt 0 ]; do# --condition 1-- ^^ --condition 2-# If variable "$remaining" is not zero length#+ AND (-a)#+ variable "$retry" is greater-than zero#+ then#+ the [ expresion-within-condition-brackets ] returns success (0)#+ and the while-loop executes an iteration.# ==============================================================# Evaluate "condition 1" and "condition 2" ***before***#+ ANDing them. Why? Because the AND (-a) has a lower precedence#+ than the -n and -gt operators,#+ and therefore gets evaluated *last*.#################################################################if [ -f /etc/sysconfig/i18n -a -z "${NOLOCALE:-}" ] ; then# Again, separate the conditions:if [ -f /etc/sysconfig/i18n -a -z "${NOLOCALE:-}" ] ; then# --condition 1--------- ^^ --condition 2-----# If file "/etc/sysconfig/i18n" exists#+ AND (-a)#+ variable $NOLOCALE is zero length#+ then#+ the [ test-expresion-within-condition-brackets ] returns success (0)#+ and the commands following execute.## As before, the AND (-a) gets evaluated *last*#+ because it has the lowest precedence of the operators within#+ the test brackets.# ==============================================================# Note:# ${NOLOCALE:-} is a parameter expansion that seems redundant.# But, if $NOLOCALE has not been declared, it gets set to *null*,#+ in effect declaring it.# This makes a difference in some contexts. | | To avoid confusion or error in a complex sequence of test operators, break up the sequence into bracketed sections. if [ "$v1" -gt "$v2" -o "$v1" -lt "$v2" -a -e "$filename" ]# Unclear what's going on here...if [[ "$v1" -gt "$v2" ]] || [[ "$v1" -lt "$v2" ]] && [[ -e "$filename" ]]# Much better -- the condition tests are grouped in logical sections. | | |
| |