Bizarre issue with printf in bash script:“09” and

2019-01-06 16:06发布

问题:

This is my bash script - I just want to left-pad a set of numbers with zeroes:

printf "%04d" "09"
printf "%04d" "08"
printf "%04d" "07"
printf "%04d" "06"

Output:

./rename.sh: line 3: printf: 09: invalid number 
0000
./rename.sh: line 4: printf: 08: invalid number 
0000 
0007
0006

What...?

Only 09 and 08 are causing the problem: every other number in my sequence seems to be OK.

回答1:

If you have your "09" in a variable, you can do

a="09"
echo "$a"
echo "${a#0}"
printf "%04d" "${a#0}"

Why does this help? Well, a number literal starting with 0 but having no x at the 2nd place is interpreted as octal value.

Octal value only have the digits 0..7, 8 and 9 are unknown.

"${a#0}" strips one leading 0. The resulting value can be fed to printf then, which prints it appropriately, with 0 prefixed, in 4 digits.

If you have to expect that you get values such as "009", things get more complicated as you'll have to use a loop which eliminates all excess 0s at the start, or an extglob expression as mentioned in the comments.



回答2:

Numbers beginning with "0" are treated as octal (i.e. base-8). Therefore, "8" and "9" aren't valid digits.

See http://www.gnu.org/software/bash/manual/bashref.html#Shell-Arithmetic.

This behaviour is inherited from languages like C.



回答3:

Bash's numeric arithmetic evaluation syntax (( ... )) can convert to base 10 (therefor ensuring correct interpretation) with the following syntax: (( 10#$var )). Or, in the case of a raw number: (( 10#08 )). Very simple & clean and can be used anywhere you're sure the base should be 10, but can't guarantee a leading zero won't be included.

So, in your example it would be as follows:

printf "%04d\n" $(( 10#09 ))
printf "%04d\n" $(( 10#08 ))
printf "%04d\n" $(( 10#07 ))
printf "%04d\n" $(( 10#06 ))

Producing the following output:

0009
0008
0007
0006

With this syntax, since you're then working with the value of the variable instead of variable itself, incrementors (( var++ )) & decrementors (( var-- )) won't work, but can still be relatively cleanly implemented as var=$(( 10#var + 1 )) and var=$(( 10#var - 1 )), respectively.

I first encountered this solution here, but this answer to a similar Stack Overflow question also demonstrates it.



回答4:

Just to add to Oli's answer, in order to pad a number with zeroes it is enough to put a 0 after the %, as you did:

printf "%04d" "9"



回答5:

Floating point is handled differently:

printf "%04.f" "009"

This gives the correct output, without dealing with any fancy bashisms (per @Oli Charlesworth's answer, 0*** is treated as octal, but I believe that Bash ignores octal/hex identifiers for floating-point numbers)



回答6:

Adding * makes shell parameter expansion matching greedy (see, for example, Shell Tipps: use internal string handling)!

# strip leading 0s
- a="009"; echo ${a##0}
+ a="009"; echo ${a##*0}