How to iterate a zero padded integer in bash?

8,193

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
Share:
8,193

Related videos on Youtube

jmunsch
Author by

jmunsch

I dig python and javascript.

Updated on September 18, 2022

Comments

  • jmunsch
    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
    jmunsch over 9 years
    I know perl is included in Ubuntu, but is it a bash builtin? I have exactly 0 experience writing in perl. This clearly works.
  • choroba
    choroba over 9 years
    @jmunsch: Perl is not a part of bash. It's a programming language of its own.
  • jmunsch
    jmunsch over 9 years
    I 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
    jmunsch over 9 years
    Would 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
    choroba over 9 years
    @jmunsch: I'd rather use pipes.