13

I have a file in the name of Build.number with the content value 012 which I need to increment by +1. So, I tried this

BN=$($cat Build.number)
BN=$(($BN+1))
echo $BN >Build.number

but here I am getting the value 11 when I am expecting 013. Can anyone help me?

  • There are two good answers, both not only explaining the cause of the problem, but providing additional information that might be useful. Stephen's provides a reference and Stéphane's mentions other shells and provides a more efficient way of reading the data without using the cat program. The ideal answer would combine both. (The ideal answer would also use "%.3d\n" format rather than the old leading 0 trick, which is ancient style, and misleading because in that case the leading 0 doesn't mean octal.) – Ray Butterworth Oct 9 at 13:22
  • 3
    @Ray why is .3d better than 03d? Apart from the octal confusion... – Stephen Kitt Oct 9 at 14:13
  • 3
    @StephenKitt. For d, i, o, u, x, X, a, A, e, E, f, F, g, and G conversion specifiers, leading zeros (following any indication of sign or base) are used to pad to the field width rather than performing space padding, except when converting an infinity or NaN. If the 0 and - flags both appear, the 0 flag is ignored. For d, i, o, u, x, and X conversion specifiers, if a precision is specified, the 0 flag shall be ignored. [CX] [Option Start] If the 0 and <apostrophe> flags both appear, the grouping characters are inserted before zero padding. For other conversions, the behavior is undefined. – Ray Butterworth Oct 9 at 16:27
  • 4
    @StephenKitt, the .3d makes the intent much more obvious: An optional precision that gives the minimum number of digits to appear for the d, i, o, u, x, and X conversion specifiers. The 03d syntax dates back to the original 1970s version, and preserving backward compatibility has made its rules extremely complicated. – Ray Butterworth Oct 9 at 16:28
31

The leading 0 causes Bash to interpret the value as an octal value; 012 octal is 10 decimal, so you get 11.

To force the use of decimal, add 10# (as long as the number has no leading sign):

BN=10#$(cat Build.number)
echo $((++BN)) > Build.number

To print the number using at least three digits, use printf:

printf "%.3d\n" $((++BN)) > Build.number
  • It would be nice to have an answer that's not a bashism, since most people (need to) write #!/bin/sh scripts not bash scripts. – R.. Oct 12 at 13:19
  • 1
    @R.. have it here – mosvy Oct 13 at 7:56
18

bash treats constants that start with 0 as octal numbers in its arithmetic expressions, so 011 is actually 9.

That's actually a POSIX requirement.

Some other shells like mksh or zsh ignore it (unless in POSIX compliant mode) as it gets in the way far more often than it is useful.

With ksh93, BN=011; echo "$(($BN))" outputs 9, but echo "$((BN))" outputs 11.

In bash, you can use BN=$((10#$(<Build.number))), which should work as long as the number doesn't start with - or +.

4

In any POSIX shell, you can prevent a number from being considered octal by stripping its leading zeros with a combination of the ${var#prefix} and ${var%%suffix} expansion forms:

BN=001002; BN=$(( ${BN#${BN%%[!0]*}} + 1 )); echo "$BN"
1003

In shells which support the ${var//pat/repl} syntax, you can also do that by prepending it a 1 and subtracting it from 10^{number_of_digits}:

BN=000012; BN=$(( 1$BN - 1${BN//?/0} )); echo "$BN"; BN=$((BN+1)); echo "$BN"
12
13

This works in bash, zsh, ksh93, mksh and yash.

In bash, ksh93 and zsh (but not in yash and mksh) you can also use the fortranish ** operator (exponentiation):

BN=000012; BN=$(( 1$BN - 10**${#BN} ))
  • 1
    For any shell, use: BN=$(( 1$BN - 1$(printf "%0*d\n" "${#BN}" 0) )) – Isaac Oct 11 at 15:13
0

Here is a function to increment a numeric string. It observes leading zeros and tries to preserve the number of digits. It uses no variables, so it doesn't require any shell extension for declaring local variables, and doesn't pollute the variable namespace:

# $1 -- decimal string, possibly with leading zeros or sign
# $2 -- integer increment, no leading zeros.
incnumstr()
{
  if [ $1 -lt 0 ] ; then
    set -- $(incnumstr ${1#-} $((- $2)))
    [ $1 -le 0 ] && printf "%s" ${1#-}  \
                 || printf "%s" -$1
    return
  fi

  set -- ${1#-} $2 # strip leading minus from zero

  [ $1 -eq 0 ] && printf "%s%0.*d" "$3" ${#1} $2 \
               || printf "%s%0.*d" "$3" ${#1} $(( ${1#${1%%[1-9]*}} + $2 ))
}

Interactive tests:

$ echo $(incnumstr 0 0)
0
$ echo $(incnumstr -0 0)
0
$ echo $(incnumstr 0 1)
1
$ echo $(incnumstr 0 -1)
-1
$ echo $(incnumstr 00 1)
01
$ echo $(incnumstr 00 -1)
-01
$ echo $(incnumstr -10 10)
00
$ echo $(incnumstr -10 11)
01
$ echo $(incnumstr -10 20)
10
$ echo $(incnumstr -10 99)
89
$ echo $(incnumstr -10 110)
100
$ echo $(incnumstr 100 -90)
010
$ echo $(incnumstr 100 -99)
001
$ echo $(incnumstr 100 -100)
000
$ echo $(incnumstr 100 -101)
-001
$ echo $(incnumstr 100 -1234)
-1134
$ echo $(incnumstr -0000 0)
0000
$ echo $(incnumstr -0000 1)
0001
$ echo $(incnumstr -0000 -2)
-0002

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.