176

Trying to figure out how to convert an argument to an integer to perform arithmetic on, and then print it out, say for addOne.sh:

echo $1 + 1
>>sh addOne.sh 1
prints 1 + 1
cuonglm
  • 153,898
user135986
  • 1,769
  • 2
  • 11
  • 3

8 Answers8

196

In bash, one does not "convert an argument to an integer to perform arithmetic". In bash, variables are treated as integer or string depending on context.

(If you are using a variable in an integer context, then, obviously, the variable better contain a string that looks like a valid integer. Otherwise, you'll get an error.)

To perform arithmetic, you should invoke the arithmetic expansion operator $((...)). For example:

$ a=2
$ echo "$a + 1"
2 + 1
$ echo "$(($a + 1))"
3

or generally preferred:

$ echo "$((a + 1))"
3

You should be aware that bash (as opposed to ksh93, zsh or yash) only performs integer arithmetic. If you have floating point numbers (numbers with decimals), then there are other tools to assist. For example, use bc:

$ b=3.14
$ echo "$(($b + 1))"
bash: 3.14 + 1: syntax error: invalid arithmetic operator (error token is ".14 + 1")
$ echo "$b + 1" | bc -l
4.14

Or you can use a shell with floating point arithmetic support instead of bash:

zsh> echo $((3.14 + 1))
4.14
John1024
  • 74,655
  • 4
    Using $(( )) or (( )) is unsafe if you don't know what the strings are (like user input). Consider this: foo=foo (( foo += 0 )) This will crash the script as it tries to recursively evaluate foo. Same with: foo=foo foo=$(( foo + 0 )) – art Aug 05 '16 at 02:00
  • 1
    This only works if you know that the variable holds a valid integer. I have lots of tests in my answer https://stackoverflow.com/a/59781257/117471 – Bruno Bronosky Jan 17 '20 at 05:02
  • This is not true for shift command: "In bash, variables are treated as integer or string depending on context." – boctulus May 08 '22 at 22:38
  • You can tell bash to guarantee a variable is a number with delcare -i. For example, declare -i x; x=string; echo $x; will print 0 – JM0 Sep 12 '23 at 02:56
  • It doesn't work to use a VAR inside for i in {1..$var} I mean, this does NOT convert str to int as required. Solution is eval. – Sergio Abreu Oct 20 '23 at 17:37
  • Also, if the string of digits contains a leading 0 it will interpret it as octal, which is not always desirable. – runlevel0 Nov 01 '23 at 14:28
24

Other way, you can use expr

Ex:

$ version="0002"
$ expr $version + 0
2
$ expr $version + 1
3
Nam
  • 249
  • It doesn't work to use a VAR inside for i in {1..$var} I mean, this does NOT convert str to int as required. Solution is eval. – Sergio Abreu Oct 20 '23 at 17:38
21

In bash, you can perform the converting from anything to integer using printf -v:

printf -v int '%d\n' "$1" 2>/dev/null

Floating number will be converted to integer, while anything are not look like a number will be converted to 0. Exponentiation will be truncated to the number before e

Example:

$ printf -v int '%d\n' 123.123 2>/dev/null
$ printf '%d\n' "$int"
123
$ printf -v int '%d\n' abc 2>/dev/null
$ printf '%d\n' "$int"
0
$ printf -v int '%d\n' 1e10 2>/dev/null
$ printf '%d\n' "$int"
1
cuonglm
  • 153,898
  • 7
    In other shells without printf -v this can be achieved with command substitution: int="$(printf '%d' 123.123 2>/dev/null)" – Adrian Günter Apr 13 '18 at 19:11
  • 2
    Re: "Floating number will be converted to integer", passing anything other than int is undefined (that's why there's an error message that needed to be redirected to stderr, even as it appears to truncate at the decimal), so instead, just format as float with zero decimals, eg, printf '%.0f' 123.123 results in 123 without any error message. (Obviously works for integer arguments as well.) – michael Oct 31 '21 at 05:53
17

A similar situation came up recently when developing bash scripts to run in both Linux and OSX environments. The result of one command in OSX returned a string containing the result code; i.e., " 0". Of course, this failed to test correctly in the following case:

if [[ $targetCnt != 0 ]]; then...

The solution was to force (i.e., 'convert') the result to an integer, similar to what @John1024 answered above so that it works as expected (Note integer comparison as opposed to string comparison):

targetCnt=$(($targetCnt + 0))
if [[ $targetCnt -ne 0 ]]; then...
JESii
  • 458
  • 5
    == etc in [[ (also [ aka test) do string comparison. There are different operators for arithmetic comparison e.g. [[ $targetcnt -ne 0 ]]; see manpage (or info) under Conditional Expressions. To trim spaces specifically, you could use [ with unquoted variable expansion [ $targetcnt == 0 ] to get default wordsplitting (NOT done in [[) but in general that approach leads you into danger. – dave_thompson_085 Aug 05 '16 at 07:34
  • For numeric comparison targetCnt=' 0'; [[ targetCnt -ne 0 ]] would have worked – Chris Davies Jan 09 '23 at 17:07
5

According to Bash documentation, the syntax for the evaluation of an arithmetic expression is $((expression)). For instance:

$ n=1
$ echo $((n+1))
2

You can use this in a script by assigning an argument to a variable, and then using arithmetic expansion:

n=$1
echo $((n+1))

Test it out:

$ bash ./test.sh 1
2
$ bash ./test.sh 7
8
4

Do not use $((n)) or similar (e.g., $((n + 0)) et al) if your number may have leading zero(es). Otherwise, your number will be treated as octal. See the following example:

n="057"
n=$((n + 0)
echo $n

Result:

47
AdminBee
  • 22,803
Tim
  • 41
  • 3
    Note that you can force decimal interpretation even with a leading zero using 10#, for example $((10#$n)) – pamphlet Dec 28 '21 at 20:11
0

Many answers that manipulate as integers, but none of them ACTUALLY converted the var string to INT, as the post asks. They don't solve the problem because they do NOT transform '5' to real 5 for situations like:

for i in {1..$myvar} # number in myvar is seen as string here! 

But there is a way, it is to EVAL a string version of the command.

eval "for i in {1..$myvar}; do something; done"
0

Other way to manipulate $vars as integers (for real conversion, use eval + string version of the command) is to use let:

i=0
let "i = $i + 1"     # i now is 1
echo $i
let "i = $i + 1"     # i now is 2
echo $i