Sample output
$ date +%m
09
$ month=$(date +%m); echo $(( month + 1 ))
bash: 09: value too great for base (error token is "09")
$ echo $(( 10#$(date +%m) ))
9
The error is the shell telling you something true in a confusing way. It read 09 as an octal constant because of the leading zero, and 9 is not a digit that exists in base 8, so the value really is too great for the base it was handed. The 10# prefix removes the guesswork by naming the base, and the same fix works on anything padded: a day, an hour, a build number sliced out of a filename.
When you would reach for it
Any time a number arrives as text with a zero on the front and you need to add, compare, or index with it. That covers most of date, which pads %m, %d, %H, %M, %S, and %j by specification, along with sequence numbers in filenames, ISO week numbers, and octets pulled out of an IP address with cut. The failure has a season. Padded values from 01 to 07 are valid octal and happen to equal their decimal reading, so a script can run correctly for seven months and then break on the eighth day of the month, or in August, depending on which field it touches.
Gotchas
- The error is the good outcome.
08and09fail loudly, but010and031are valid octal and evaluate silently to 8 and 25. A script that compares$(date +%j)against a threshold is quietly six off on the 31st of January and never says a word about it. If you only defend against the error message, you have fixed the half of the problem that was already telling you it was broken. - The prefix is picky about what follows it.
$(( 10#$m ))works, but$(( 10#m ))fails with the same “value too great for base” message, because the prefix expects literal digits andmis not one, even though bare$(( m ))is the normal way to reference a variable. An empty value is a different error:$(( 10#$m ))withmunset becomes10#on its own and reports an invalid integer constant, so guard it with$(( 10#${m:-0} )). - Your interactive shell may not reproduce the bug. zsh leaves its
OCTAL_ZEROESoption off by default, specifically because the rule breaks date and time strings, so$(( 09 + 1 ))returns 10 in a macOS login shell and then fails the moment the same line runs under#!/bin/bash. Test under the shebang you ship, not the shell you type in. 10#is not portable to POSIX sh. dash rejects10#08as a syntax error, and it rejects a bare08as well, so a#!/bin/shscript gets no fix and no free pass. See the variants for the portable form.printfcarries the same C rules in its numeric conversions.printf '%d' 08refuses with “invalid octal number”, andprintf '%d' 010prints 8 without comment. Passing a padded value to%dis the same trap wearing different clothes.
Variants
$ echo $(( 16#1f4 )) $(( 2#11010110 )) $(( 8#755 ))
Any base from 2 to 64 in, decimal out. Digits run 0-9, then a-z, then A-Z, then @ and _ for bases above 36, and case only matters once the base passes 36
$ printf '%#x %#o\n' 500 500
The trip back out. $(( )) only ever returns decimal, so conversion in the other direction belongs to printf, and %# adds the 0x and 0 prefixes that the shell will read back correctly
$ m=09; echo $(( ${m#0} + 1 ))
The portable fix for #!/bin/sh, using parameter expansion to strip one leading zero before the arithmetic sees it. It handles two-character fields; for %j and its three characters you need the strip twice or expr "$m" + 1, which treats its arguments as decimal and has no opinion about zeros