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?

7
  • 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.) Commented Oct 9, 2019 at 13:22
  • Related: unix.stackexchange.com/a/36959/117549 Commented Oct 9, 2019 at 13:48
  • 3
    @Ray why is .3d better than 03d? Apart from the octal confusion... Commented Oct 9, 2019 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. Commented Oct 9, 2019 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. Commented Oct 9, 2019 at 16:28

4 Answers 4

33

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
4
  • 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. Commented Oct 12, 2019 at 13:19
  • 1
    @R.. have it here Commented Oct 13, 2019 at 7:56
  • @Stephen Kitt i am trying to run the following method in powershell. But its not taking up, getting bad arithmetic expression. is there any other method can be used for running in powershell Commented Jan 6, 2020 at 6:58
  • @Naik the answer above was written for Bash, I don’t know the equivalent in PowerShell. Commented Jan 6, 2020 at 8:01
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 +.

0
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
  • 1
    For any shell, use: BN=$(( 1$BN - 1$(printf "%0*d\n" "${#BN}" 0) )) Commented Oct 11, 2019 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

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.