(One)-minute geek news

Group talks, Laboratoire de Chimie, ENS de Lyon

Manipulating numbers in Bash


Last week, we saw how Python handles floats. Let us see now how Bash handles numbers.

Integers

Bash natively only supports basic arithmetic evaluation of fixed-width integers.

This evaluation is performed through Arithmetic expansion using the syntax:

  • $((expression))
  • or the deprecated format $[expression]

Note: While divisions by zero are flagged, overflows are not checked: so that echo $((2**63)) will likely return a negative number on a 64-bit machine, without any warning!

However, the conversion of constants from string to integer can be tricky. Indeed, Bash supports implicit conversion from different bases, which can lead to confusing situations.

Hexadecimals

Constants starting with 0x or 0X are considered as hexadecimal numbers.

Example: $((0x10 == 16)) or $((0XaB == 10*16+11)) are evaluated as true (i.e. 1)

Octals

Constants starting with 0 (followed by numbers only) are considered as octal numbers.

Example: $((010 == 8)) is evaluated as true (i.e. 1)

Beware that octal numbers cannot have digits 8 or 9: $((009 + 1)) will throw an error.

Decimals

Otherwise, constants are considered as decimal numbers.

Explicit base

Sometimes, we need to manipulate decimals that do have leading zeros (from filenames, dates, ...). So how can we work with them?

Bash allows to explicitely set the arithmetic base that must be used to convert an integer. The syntax is base#number

Example: $((10#010 == 10)) or $((2#101010 == 42)) are evaluated as true (i.e. 1)

Bonus: base must be a decimal number between 2 and 64. For bases larger than 36, the alphabet is ordered as follows: {0..9} {a..z} {A..Z} @ _. For bases less or equal to 36, lowercase and uppercase letters can be used interchangeably.

Floats and more advanced calculus

If you need to handle floats or perform more advanced calculus in your Bash scripts, you can use the bc utility, with the -l option to treats floats (20 digits precision) and have access to a basic math library:

Example: echo "sqrt(2^3)"|bc -l or bc -l <<< "1/3"

Bonus: You can set a custom arbitrary precision with the scale internal variable: bc -l <<< "scale=4200 ; 1/3". I then advise you to disable the line-breaking feature by setting the environment parameter BC_LINE_LENGTH to 0: BC_LINE_LENGTH=0 bc -l <<< "scale=4200 ; 1/3"

Bonus (bis): You can even set the output and input bases (in that order): bc -l <<< 'obase=2;ibase=16; 2A * 10' would print 672 in binary (i.e. 42 * 16)

Bonus: quick calculator

Simply add the following line to your .bashrc to be able to use a convenient calculator quickly: ? 7*42 - 6 for example (works only if your current working directory does not contain a file whose name is only one letter, since ? is then a valid pattern for path substitution)

? () { BC_LINE_LENGTH=0 bc -l <<< "$*"; }

Enjoy!

References

man bash
man bc