How to iterate a zero padded integer in bash?
Solution 1
In bash
, numbers with leading zeros are considered as octal. To force bash
to consider them as decimal, you can add a 10#
prefix:
next_number=$(printf %06d "$((10#$current_number + 1))")
Or with bash 3.1 or above, to avoid the forking:
printf -v next_number %06d "$((10#$current_number + 1))"
(note that it doesn't work for negative numbers as 10#-010
is seen as 10#0 - 010
in bash
, so both $((10#-10))
and $((-10#-10))
expand to -8
).
See also:
$ printf 'A%06d\n' {5..12}
A000005
A000006
A000007
A000008
A000009
A000010
A000011
A000012
Or:
$ printf '%s\n' {A..C}{00008..00012}
A00008
A00009
A00010
A00011
A00012
B00008
B00009
B00010
B00011
B00012
C00008
C00009
C00010
C00011
C00012
Or:
$ seq -f A%06g 5 12
A000005
A000006
A000007
A000008
A000009
A000010
A000011
A000012
Solution 2
Perl to the rescue:
perl -E '$x = "A001"; say $x++ for 1 .. 1002'
Output:
A001
A002
A003
A004
A005
A006
A007
A008
A009
A010
A011
...
A996
A997
A998
A999
B000
B001
B002
The ++ operator handles both letters and numbers.
Solution 3
For historical reasons, numerical operations in Bourne/POSIX-style shells parse integer constants with a leading 0
digit as octal rather than decimal. So 08
in an arithmetic operating is a syntax error, the successor of 07
is 8, and 010
is equivalent to 8
.
You can use normal arithmetic and then pad the numbers when printing them with the printf
builtin.
next_number=$(($current_number + 1))
printf -v padded_next_number %06d "$next_number"
The -v
option to printf
is bash-specific; the POSIX way is
next_number=$(($current_number + 1))
padded_next_number=$(printf %06d "$next_number")
Here's another approach which was useful in historical systems that didn't have a printf
command, and can still be useful for performance in the few shells that don't have printf
as a builtin. Instead of counting from 1, count from 1000001. That way, your numbers never have to have leading zeroes. When you use the number, strip off the leading 1
digit.
number=1000000
while … ; do
number=$((number+1))
echo "${number#1}"
…
done
Related videos on Youtube
Comments
-
jmunsch almost 2 years
How does one iterate a string with the form "[A-Z][0-9]*" Or for example: "A000001"?
After receiving the variable I split:
current_=$(mysql -h"$mysqlhost" -u"$mysqluser" -p"$PASS" "$DBNAME" -se "SELECT current_ FROM $GLOBALDB;") current_number=$(echo $current_ | grep -oh "[0-9]*") current_letter=$(echo $current_ | grep -oh "[A-Z]*")
However when I try to add 1:
# add 1 & keep all leading zeros "000001" next_number=$(printf %06d $(($current_number + 1)))
It counts to "000009" and rolls back over to "000000".
And I join as follows:
next_=$(echo "$current_letter$next_number")
And in regards to the Letter iteration I was thinking of using an Associated Array? Or brace expansion
{A..Z}
, but that is a whole different question. -
jmunsch over 9 yearsI know perl is included in Ubuntu, but is it a bash builtin? I have exactly 0 experience writing in perl. This clearly works.
-
choroba over 9 years@jmunsch: Perl is not a part of bash. It's a programming language of its own.
-
jmunsch over 9 yearsI did some searching. It looks as though Perl is in a lot of distros? And adding a dependency on Perl is not a problem? Thanks for your answer.
-
jmunsch over 9 yearsWould this be a better perl answer?
export current_ && perl -E 'say $ENV{current_}++'
because I wasn't able to import the variable from bash to perl? -
choroba over 9 years@jmunsch: I'd rather use pipes.