Differences between revisions 26 and 27
Revision 26 as of 2014-02-13 18:16:48
Size: 6662
Editor: 173-228-24-96
Comment:
Revision 27 as of 2016-06-24 18:09:21
Size: 8884
Editor: GreyCat
Comment: Rewrite. Rearrange.
Deletions are marked like this. Additions are marked like this.
Line 3: Line 3:
Also see [[http://wiki.bash-hackers.org/syntax/arith_expr|the Bash hackers article]] about the full syntax theory. /!\ The {{{$[ ]}}} syntax is [[http://wiki.bash-hackers.org/syntax/expansion/arith|deprecated]] /!\ The `$[ ]` syntax is [[http://wiki.bash-hackers.org/syntax/expansion/arith|deprecated]]. Please use `$(( ))` instead.
Line 5: Line 5:
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: == Arithmetic Expansion ==

POSIX sh (and all shells based on it, including Bash and ksh) uses the `$(( ))` syntax to do arithmetic, using the same syntax as C. (See [[http://wiki.bash-hackers.org/syntax/arith_expr|the Bash hackers article]] for the full syntax.) Bash calls this an "Arithmetic Expansion", and it obeys the same basic rules as all other `$...` substitutions.

`$(( ))` is the first example of a ''math context'', meaning a context where the syntax and semantics of C's integer arithmetic are used. This will be discussed in more detail below.

Here are a few examples using `$(( ))`:

{{{
# POSIX sh
i=$((j + 3))
lvcreate -L "$((24 * 1024))" -n lv99 vg99
q=$((29 / 6)) r=$((29 % 6))
if test "$((a%4))" = 0; then ...
}}}

Notes:
 * Arithmetic operators include all of the C operators (arithmetic, bit shifting/masking, ternary `?:`), plus `**` for exponentiation.
 * You may use commas to separate multiple expressions within a single math context. The final ''value'' of the arithmetic expression is that of the last comma-delimited expression.
 * All arithmetic is done with 64-bit signed integers (as of Bash 2.05b -- may be different in other shells).
 * Variable names in a math context are substituted with their values, as long as those values are valid integers. In Bash, this substitution continues recursively until an integer or an invalid variable name is generated. Unset or empty variables are evaluated as 0.
 * Numbers without leading 0 are treated as base 10. Numbers with a leading 0x are treated as base 16. Numbers with a leading 0 (not followed by x) are treated as base 8. You may put `10#` or `16#` (etc.) in front of a number to force it to be interpreted in a given base -- more on this later.

== Arithmetic Commands ==

Bash also offers two forms of ''commands'' that use a math context. The expressions within the math context follow the same rules as the expressions inside `$(( ))`, but since we have a command, we also get an exit status, and side effects.

The exit status is based on the value of the (last) expression. If the expression evaluates to 0, the command is considered "false" and returns 1. Otherwise, the command is considered "true" and returns 0. See below for examples of this.

The first arithmetic command is `let`:
Line 11: Line 40:
Note that each arithmetic expression has to be passed as a single argument to the {{{let}}} command, so you need quotes if there are spaces or globbing characters, thus:
Note that each arithmetic expression has to be passed as a single argument to the `let` command, so you need quotes if there are spaces or globbing characters.  Thus:
Line 21: Line 51:
let a\[1]=1+1  # Right let a\[1\]=1+1 # Right, but very odd.
Line 23: Line 53:
Division in Bash is integer division, and it truncates the results, just as in C:
Line 25: Line 54:
{{{
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 {{{let}}}, and variables inside {{{(( ))}}} don't require {{{$}}} (because string literals aren't allowed). Examples:
The second command is `(( ))`. It works identically to `let`, but you don't need to quote the expressions because they are delimited by the `((` and `))` syntax. For example:
Line 38: Line 63:
}}}
Line 39: Line 65:
# (( )) may also be used as a command. > or < inside (( )) means
# greater/less than, not output/input redirection.
`>` or `<` inside `(( ))` means greater/less than, not output/input redirection. An expression containing one of these evalutes to either 1 (if the comparison is true) or 0 (if false).

{{{
Line 43: Line 70:
{{{(( ))}}} without the leading {{{$}}} is not a standard sh feature. It comes from ksh and is only available in ksh, Bash and zsh. {{{$(( ))}}} 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. Like for parameter substitution, arithmetic substitution is subject to word splitting so should be quoted to prevent it when in list contexts. Here are some examples of the use of the arithmetic substitution syntax:
Line 45: Line 71:
{{{
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 {{{declare}}}d 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:
`(( ))` is used more widely than `let`, because it fits so well into an `if` or `while` command.
Line 52: Line 73:
{{{
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 [[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.

{{{
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.
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, when evaluating an arithmetic expression, C language rules (0 is false, anything else is true) apply.
Line 104: Line 80:
echo "$((10 > 6))" # Writes 1. An arithmetic expression returns 1 for true. echo "$((10 > 6))" # Writes 1. An arithmetic expression evaluates to 1 for true.
Line 106: Line 82:
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.
In addition to a comparison returning 1 for true, an arithmetic command that ''evaluates'' to any non-zero value returns true as an exit status.
Line 111: Line 88:
Line 122: Line 100:

This also means that every arithmetic command we run is returning an exit status. Most scripts ignore these, but if you are using [[BashFAQ/105|set -e]] you may be unpleasantly surprised when your program ''aborts'' because you assigned 0 to a number.

== Math Contexts ==

As we've seen, the insides of `$(( ))` and `(( ))`, and the arguments of `let`, are math contexts.

Also, (non-associative) [[BashFAQ/005|array]] indices are a math context:

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

In case it wasn't obvious, the `(( ))` in a C-style `for` command are a math context. Or three separate math contexts, depending on your point of view.

{{{
for ((i=0, j=0; i<100; i++, j+=5)); do ...
}}}

Finally, the ''start'' and ''length'' arguments of Bash's `${var:start:length}` [[BashFAQ/073|parameter expansion]] are math contexts.

{{{
echo "${string:i+2:len-2}"
}}}

== Leading Zeros and Base Selection ==

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 (dates and times 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.

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 [[glob|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.

With an extended glob:

{{{
shopt -s extglob

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

The second 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.
}}}
Line 125: Line 170:
todec() { frombase() {
Line 128: Line 173:

# Examples:

frombase 16 ffe # prints 4094
frombase 2 100100 # prints 36
Line 129: Line 179:
Examples:
== Integer Declaration ==

/!\ '''Integer delcaration is considered harmful.''' Don't do this. It makes your code difficult to read, because you need to know whether a given variable is declared as an integer or not, to know what a command does.

Variables may be {{{declare}}}d 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:
Line 132: Line 187:
todec 16 ffe # -> 4094
todec 2 100100 # -> 36
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
Line 135: Line 192:

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.

/!\ The $[ ] syntax is deprecated. Please use $(( )) instead.

Arithmetic Expansion

POSIX sh (and all shells based on it, including Bash and ksh) uses the $(( )) syntax to do arithmetic, using the same syntax as C. (See the Bash hackers article for the full syntax.) Bash calls this an "Arithmetic Expansion", and it obeys the same basic rules as all other $... substitutions.

$(( )) is the first example of a math context, meaning a context where the syntax and semantics of C's integer arithmetic are used. This will be discussed in more detail below.

Here are a few examples using $(( )):

# POSIX sh
i=$((j + 3))
lvcreate -L "$((24 * 1024))" -n lv99 vg99
q=$((29 / 6)) r=$((29 % 6))
if test "$((a%4))" = 0; then ...

Notes:

  • Arithmetic operators include all of the C operators (arithmetic, bit shifting/masking, ternary ?:), plus ** for exponentiation.

  • You may use commas to separate multiple expressions within a single math context. The final value of the arithmetic expression is that of the last comma-delimited expression.

  • All arithmetic is done with 64-bit signed integers (as of Bash 2.05b -- may be different in other shells).
  • Variable names in a math context are substituted with their values, as long as those values are valid integers. In Bash, this substitution continues recursively until an integer or an invalid variable name is generated. Unset or empty variables are evaluated as 0.
  • Numbers without leading 0 are treated as base 10. Numbers with a leading 0x are treated as base 16. Numbers with a leading 0 (not followed by x) are treated as base 8. You may put 10# or 16# (etc.) in front of a number to force it to be interpreted in a given base -- more on this later.

Arithmetic Commands

Bash also offers two forms of commands that use a math context. The expressions within the math context follow the same rules as the expressions inside $(( )), but since we have a command, we also get an exit status, and side effects.

The exit status is based on the value of the (last) expression. If the expression evaluates to 0, the command is considered "false" and returns 1. Otherwise, the command is considered "true" and returns 0. See below for examples of this.

The first arithmetic command is let:

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

Note that each arithmetic expression has to be passed as a single argument to the let command, so you need quotes if there are spaces or globbing characters. Thus:

let a=17 + 23      # WRONG
let a="17 + 23"    # Right
let 'a = 17 + 23'  # Right
let a=17 a+=23     # Right (2 arithmetic expressions)

let a[1]=1+1       # Wrong (try after touch a1=1+1 or with shopt -s failglob)
let 'a[1]=1+1'     # Right
let a\[1\]=1+1     # Right, but very odd.

The second command is (( )). It works identically to let, but you don't need to quote the expressions because they are delimited by the (( and )) syntax. For example:

((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.

> or < inside (( )) means greater/less than, not output/input redirection. An expression containing one of these evalutes to either 1 (if the comparison is true) or 0 (if false).

if ((a > 5)); then echo "a is more than 5"; fi

(( )) is used more widely than let, because it fits so well into an if or while command.

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, when evaluating an arithmetic expression, 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 evaluates to 1 for true.

In addition to a comparison returning 1 for true, an arithmetic command that evaluates to any non-zero value returns true as an exit status.

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 ...

This also means that every arithmetic command we run is returning an exit status. Most scripts ignore these, but if you are using set -e you may be unpleasantly surprised when your program aborts because you assigned 0 to a number.

Math Contexts

As we've seen, the insides of $(( )) and (( )), and the arguments of let, are math contexts.

Also, (non-associative) array indices are a math context:

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

In case it wasn't obvious, the (( )) in a C-style for command are a math context. Or three separate math contexts, depending on your point of view.

for ((i=0, j=0; i<100; i++, j+=5)); do ...

Finally, the start and length arguments of Bash's ${var:start:length} parameter expansion are math contexts.

echo "${string:i+2:len-2}"

Leading Zeros and Base Selection

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 (dates and times 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.

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.

With an extended glob:

shopt -s extglob

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

The second 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.

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

frombase() {
    echo "$(( $1#$2 ))"
}

# Examples:

frombase 16 ffe    # prints 4094
frombase 2 100100  # prints 36

Integer Declaration

/!\ Integer delcaration is considered harmful. Don't do this. It makes your code difficult to read, because you need to know whether a given variable is declared as an integer or not, to know what a command does.

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


CategoryShell

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