Differences between revisions 3 and 13 (spanning 10 versions)
Revision 3 as of 2005-08-27 14:36:22
Size: 4390
Editor: GreyCat
Comment: 0/1/true/false fun
Revision 13 as of 2009-10-12 20:20:26
Size: 6133
Comment: added a function to convert numbers in other bases to base 10
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
Arithmetic in ["BASH"] is integer math only. You can't do floating point math in Bash; if you need that capability, see [wiki:Self:BashFaq#faq22 Bash FAQ #22]. Arithmetic in [[BASH]] is integer math only. You can't do floating point math in Bash; if you need that capability, see [[BashFAQ/022|Bash FAQ #22]].

Also see [[http://bash-hackers.org/
wiki/doku.php?id=syntax:arith_expr|the Bash hackers article]] about the full syntax theory.
Line 24: Line 26:
In addition to the {{{let}}} command, one may use the {{{(( ))}}} syntax to enforce an arithmetic context. If there is a {{{$}}} (dollar sign) before the parentheses, then a substitution is performed. White space is allowed inside {{{(( ))}}} with much greater leniency than with normal assignments, and variables inside {{{(( ))}}} don't require {{{$}}} (because string literals aren't allowed). Examples: In addition to the {{{let}}} command, one may use the {{{(( ))}}} syntax to enforce an arithmetic context. If there is a {{{$}}} (dollar sign) before the parentheses, then a substitution is performed (more on this below). White space is allowed inside {{{(( ))}}} with much greater leniency than with normal assignments, and variables inside {{{(( ))}}} don't require {{{$}}} (because string literals aren't allowed). Examples:
Line 34: Line 36:
echo "a * b = $((a * b))" # Substitution example.
Line 39: Line 39:
}}}

{{{(( ))}}} without the leading {{{$}}} is a Bash-only feature. {{{$(( ))}}} substitution is allowed in the POSIX shell. As one would expect, the result of the arithmetic expression inside the {{{$(( ))}}} is substituted into the original command. Here are some examples of the use of the arithmetic substitution syntax:

{{{
a=$((a+7)) # POSIX-compatible version of previous code.
if test $((a%4)) = 0; then ...
lvcreate -L $((4*1096)) -n lvname vgname # Actual HP-UX example.
Line 50: Line 58:
There is one common pitfall with arithmetic expressions in Bash: numbers with leading zeroes are treated as octal. This causes great confusion among people who are extracting zero-padded numbers from various sources and then doing math on them without sanitizing them first. Also, array indices are a numeric context:

{{{
n=0
while read line; do
   array[n++]=$line # array[] forces a numeric context
done
}}}

There is one common pitfall with arithmetic expressions in Bash: numbers with leading zeroes are treated as octal. For example,

{{{
# Suppose today is September 19th.
month=$(date +%m)
next_month=$(( (month == 12) ? 1 : month+1 ))
# bash: 09: value too great for base (error token is "09")
}}}

This causes great confusion among people who are extracting zero-padded numbers from various sources (although dates are by far the most common) and then doing math on them without sanitizing them first.  (It's especially bad if you write a program like this in March, test it, roll it out... and then it doesn't blow up until August 1.)
Line 56: Line 82:
while [[ $a == 0* ]]; do a=${a/0/}; done while [[ $a = 0* ]]; do a=${a#0}; done
Line 59: Line 85:
The second way is to force Bash to treat all numbers as base 10 by prefixing them with {{{10#}}}. This is more efficient, but may be less elegant to read. You can do the above without using a loop, by using extended globs; see [[BashFAQ/067|FAQ #67]] for more information. Or, you could use {{{sed}}}; that may be more efficient if you're reading many numbers from a stream, and can arrange to sanitize them all in one command, rather than one by one.

Without a loop:

{{{
# This removes leading zeroes from a, all at once.
a=${a/*(0)/}
}}}

The third solution is to force Bash to treat all numbers as base 10 by prefixing them with {{{10#}}}. This might be more efficient, but also may be less elegant to read.
Line 77: Line 112:
In addition to a "greater than"-type expression returning 1 for true, an arithmetic expression that ''evaluates'' to a non-zero value is also true in the sense of a command. In addition to a comparison returning 1 for true, an arithmetic expression that ''evaluates'' to a non-zero value is also true in the sense of a command.
Line 86: Line 121:
ok=0
while ((! ok)); do
found=0
while ...; do
Line 89: Line 124:
  if something; then ok=1; fi # If for some reason a break isn't desired.   if something; then found=1; fi # Found one! Keep going.
 ...
Line 91: Line 127:
if ((found)); then ...
Line 92: Line 129:

Here is a function to convert numbers in other bases to decimal (base 10):

{{{
todec() {
    echo $(( $1#$2 ))
}
}}}

Examples:

{{{
todec 16 ffe # -> 4094
todec 2 100100 # -> 36
}}}


----
CategoryShell

Arithmetic in BASH is integer math only. You can't do floating point math in Bash; if you need that capability, see Bash FAQ #22.

Also see the Bash hackers article about the full syntax theory.

There are several ways to tell Bash to treat numbers as integers instead of strings, and to do basic arithmetic operations on them. The first is to use the let command:

let a=17+23
echo "a = $a"      # Prints a = 40

Note that as with any variable assignment in Bash, you need to quote the whole right hand side if it contains spaces, thus:

let a=17 + 23      # WRONG
let a="17 + 23"    # Right

Division in Bash is integer division, and it truncates the results, just as in C:

let a=28/6
echo "a = $a"      # Prints a = 4

In addition to the let command, one may use the (( )) syntax to enforce an arithmetic context. If there is a $ (dollar sign) before the parentheses, then a substitution is performed (more on this below). White space is allowed inside (( )) with much greater leniency than with normal assignments, and variables inside (( )) don't require $ (because string literals aren't allowed). Examples:

((a=$a+7))         # Add 7 to a
((a = a + 7))      # Add 7 to a.  Identical to the previous command.
((a += 7))         # Add 7 to a.  Identical to the previous command.

((a = RANDOM % 10 + 1))     # Choose a random number from 1 to 10.
                            # % is modulus, as in C.

# (( )) may also be used as a command.  > or < inside (( )) means
# greater/less than, not output/input redirection.
if ((a > 5)); then echo "a is more than 5"; fi

(( )) without the leading $ is a Bash-only feature. $(( )) substitution is allowed in the POSIX shell. As one would expect, the result of the arithmetic expression inside the $(( )) is substituted into the original command. Here are some examples of the use of the arithmetic substitution syntax:

a=$((a+7))         # POSIX-compatible version of previous code.
if test $((a%4)) = 0; then ...
lvcreate -L $((4*1096)) -n lvname vgname   # Actual HP-UX example.

Variables may be declared as integers so that any subsequent assignments to them will always assume a numeric context. Essentially any variable that's declared as an integer acts as if you had a let command in front of it when you assign to it. For example:

unset b             # Forget any previous declarations
b=7+5; echo "$b"    # Prints 7+5
declare -i b        # Declare b as an integer
b=7+5; echo "$b"    # Prints 12

Also, array indices are a numeric context:

n=0
while read line; do
   array[n++]=$line      # array[] forces a numeric context
done

There is one common pitfall with arithmetic expressions in Bash: numbers with leading zeroes are treated as octal. For example,

# Suppose today is September 19th.
month=$(date +%m)
next_month=$(( (month == 12) ? 1 : month+1 ))
# bash: 09: value too great for base (error token is "09")

This causes great confusion among people who are extracting zero-padded numbers from various sources (although dates are by far the most common) and then doing math on them without sanitizing them first. (It's especially bad if you write a program like this in March, test it, roll it out... and then it doesn't blow up until August 1.)

If you have leading-zero problems with Bash's built-in arithmetic, there are two possible solutions. The first is, obviously, to remove the leading zeroes from the numbers before doing math with them. This is not trivial in Bash, unfortunately, because Bash has no ability to perform substitutions on a variable using regular expressions (it can only do it with "glob" patterns). But you could use a loop:

# This removes leading zeroes from a, one at a time.
while [[ $a = 0* ]]; do a=${a#0}; done

You can do the above without using a loop, by using extended globs; see FAQ #67 for more information. Or, you could use sed; that may be more efficient if you're reading many numbers from a stream, and can arrange to sanitize them all in one command, rather than one by one.

Without a loop:

# This removes leading zeroes from a, all at once.
a=${a/*(0)/}

The third solution is to force Bash to treat all numbers as base 10 by prefixing them with 10#. This might be more efficient, but also may be less elegant to read.

a=008
let b=a+1       # Generates an error because 008 is not valid in octal.
let b=10#$a+1   # Force a to be treated as base 10.  Note: the $ is required.

Finally, a note on the exit status of commands, and the notions of "true" and "false", is in order. When bash runs a command, that command will return an exit status from 0 to 255. 0 is considered "success" (which is "true" when used in the context of an if or while command). However, in an arithmetic context, there are places where the C language rules (0 is false, anything else is true) apply.

Some examples:

true; echo $?        # Writes 0, because a successful command returns 0.
((10 > 6)); echo $?  # Also 0.  An arithmetic command returns 0 for true.
echo $((10 > 6))     # Writes 1.  An arithmetic expression returns 1 for true.

In addition to a comparison returning 1 for true, an arithmetic expression that evaluates to a non-zero value is also true in the sense of a command.

if ((1)); then echo true; fi     # Writes true.

This also lets you use "flag" variables, just like in a C program:

found=0
while ...; do
  ...
  if something; then found=1; fi    # Found one!  Keep going.
  ...
done
if ((found)); then ...

Here is a function to convert numbers in other bases to decimal (base 10):

todec() {
    echo $(( $1#$2 ))
}

Examples:

todec 16 ffe    # -> 4094
todec 2 100100  # -> 36


CategoryShell

ArithmeticExpression (last edited 2023-12-11 16:33:33 by GreyCat)