591

I need to loop some values,

for i in $(seq $first $last)
do
    does something here
done

For $first and $last, I need it to be of fixed length 5. So if the input is 1, I need to add zeros in front such that it becomes 00001. It loops till 99999 for example, but the length has to be 5.

E.g.: 00002, 00042, 00212, 12312 and so forth.

Any idea on how I can do that?

cpit
  • 157
  • 1
  • 1
  • 10
John Marston
  • 11,549
  • 7
  • 23
  • 18
  • 47
    A good answer could be: seq - w 1 99999. Option -w keeps output lenght constant, padding shortest numbers with 0. – Bruno von Paris Jan 27 '14 at 13:04
  • 1
    Also rename files: http://stackoverflow.com/questions/55754/bash-script-to-pad-file-names – Ciro Santilli OurBigBook.com Oct 01 '15 at 07:43
  • 1
    Many answers here recommend `for variable in $(something to generate the numbers); do ...` but this is problematic when the list of numbers is long. It is much more efficient to use `something to generate the numbers | while read -r variable; do ...`. See also https://mywiki.wooledge.org/DontReadLinesWithFor which discusses reading lines from files etc, but some of the arguments apply here too. – tripleee Nov 23 '18 at 05:40
  • 1
    See how to zero-padding with parameter expansion: https://askubuntu.com/a/1257316/670392 – Noam Manos Jul 08 '20 at 10:36

16 Answers16

956

In your specific case though it's probably easiest to use the -f flag to seq to get it to format the numbers as it outputs the list. For example:

for i in $(seq -f "%05g" 10 15)
do
  echo $i
done

will produce the following output:

00010
00011
00012
00013
00014
00015

More generally, bash has printf as a built-in so you can pad output with zeroes as follows:

$ i=99
$ printf "%05d\n" $i
00099

You can use the -v flag to store the output in another variable:

$ i=99
$ printf -v j "%05d" $i
$ echo $j
00099

Notice that printf supports a slightly different format to seq so you need to use %05d instead of %05g.

David Webb
  • 190,537
  • 57
  • 313
  • 299
  • 8
    `%05g` instead of `%05d` fixed it for me. _"00026"_ was giving me _"00022"_. Thanks! – Ed Manet Feb 21 '13 at 21:16
  • 28
    Great, comprehensive answer. One small correction: strictly speaking, `seq` supports a _subset_ of the format chars. that `printf` supports (and that subsets includes `g`, but not `d`). The behavior of characters `d` and `g` differs subtly in that `d` interprets `0`-prefixed numbers strings as _octal_ numbers and converts them to decimal, whereas `g` treats them as decimals. (@EdManet: that's why '00026' turned into '00022' with `d`). Another thing worth mentioning: `seq -w` does _automatic_ zero-padding of the output numbers based on the widest number generated. – mklement0 Jul 31 '13 at 17:30
  • This helped me generate hex characters list. `for (( i = 0; i <= 0xffffffff; i++ )) do printf "%08x\n" $i ; done >> hex.txt` produced a 8 character hexadecimal list. Thanks. – cde Jan 25 '14 at 22:32
  • `seq --help`: "FORMAT must be suitable for printing one argument of type 'double'; it defaults to %.PRECf if FIRST, INCREMENT, and LAST are all fixed point decimal numbers with maximum precision PREC, and to %g otherwise." The possible [conversion specifiers](https://jlk.fjfi.cvut.cz/arch/manpages/man/core/man-pages/printf.3.en#Conversion_specifiers) are [`efgaEFGA`](https://github.com/coreutils/coreutils/blob/v8.31/src/seq.c#L261-L263). – x-yuri Sep 30 '19 at 13:23
  • this is significantly slower than the `-w` option that seq provides. and because it passes the number through printf, if you're not running the GNU versions (i.e. running mac native versions), you'll end up with scientific notation for larger numbers (roughly any ints > 6 digits will be in the x.xxxxxxe+0y format) – Mr. T Dec 04 '21 at 02:31
197

Easier still you can just do

for i in {00001..99999}; do
  echo $i
done
Indie
  • 2,015
  • 1
  • 12
  • 2
  • 19
    This works on Bash version 4.1.2 (CentOS 6) but fails in version 3.2.25 (CentOS 5). I do agree that this is much more aesthetically pleasing way to do it! – Mike Starov Sep 13 '13 at 16:03
  • 1
    Works, but does not give zero padded strings on my bash – user2707001 Oct 19 '17 at 12:11
  • On a Mac (Bash 4.4), this doesn't pad the numbers. On CentOS & SuSE, it works great! – Stefan Lasiewski Nov 19 '18 at 17:58
  • This works fine on macOS with Bash `4.4.23(1)-release` – Whymarrh Jan 03 '19 at 22:47
  • Note: If you're looping in order to run a command, and your command can accept multiple arguments, you might be better off running the command directly. e.g. It takes around 7 seconds for my computer to loop through `for i in {0001..9000}; do; touch $i; done;`, but is almost instant (and less code) if I just do: `touch {0001..9000}` – daviewales Feb 28 '23 at 05:19
140

If the end of sequence has maximal length of padding (for example, if you want 5 digits and command is seq 1 10000), than you can use -w flag for seq - it adds padding itself.

seq -w 1 10

would produce

01
02
03
04
05
06
07
08
09
10
Adam Smooch
  • 1,167
  • 1
  • 12
  • 27
m_messiah
  • 2,115
  • 2
  • 15
  • 12
  • 7
    This is obviously the correct answer. Why does it have so few upvotes? – adius Apr 26 '17 at 16:22
  • 6
    easy, because the padding depends on the max number you want to reach. If this is a parameter you never know beforehand how many zero you will end up with – guillem Jun 22 '17 at 13:30
  • 10
    This doesn't give a specified fixed length, just results which are all of the _same_ length. – Ceisc Aug 25 '17 at 21:56
  • You can also do `seq -w 001 009` to force length 3. – isarandi Jan 24 '22 at 20:23
  • 1
    fresh seq installed in macOS does not have possiblity to do `seq -w 001 009`, so you are lucky it works for you – m_messiah Jan 26 '22 at 16:00
  • @m_messiah: The gnu version of seq on mac, installed with brew, from "coreutils" and available as `gseq`, works as mention by @isarandi, just the built-in bsd variant is too simple... – A. Rabus Aug 07 '23 at 07:48
113

use printf with "%05d" e.g.

printf "%05d" 1
frankc
  • 11,290
  • 4
  • 32
  • 49
29

Very simple using printf

[jaypal:~/Temp] printf "%05d\n" 1
00001
[jaypal:~/Temp] printf "%05d\n" 2
00002
jaypal singh
  • 74,723
  • 23
  • 102
  • 147
14

Use awk like this:

awk -v start=1 -v end=10 'BEGIN{for (i=start; i<=end; i++) printf("%05d\n", i)}'

OUTPUT:

00001
00002
00003
00004
00005
00006
00007
00008
00009
00010

Update:

As pure bash alternative you can do this to get same output:

for i in {1..10}
do
   printf "%05d\n" $i
done

This way you can avoid using an external program seq which is NOT available on all the flavors of *nix.

anubhava
  • 761,203
  • 64
  • 569
  • 643
12

I pad output with more digits (zeros) than I need then use tail to only use the number of digits I am looking for. Notice that you have to use '6' in tail to get the last five digits :)

for i in $(seq 1 10)
do
RESULT=$(echo 00000$i | tail -c 6)
echo $RESULT
done
unifex
  • 165
  • 1
  • 6
  • 1
    If you wanted to use the correct number of places in the -c argument of tail, you could use 'echo -n 00000$i' as this stops it outputting a newline which is one of the chars tail is returning, hence it needing to be one higher in this answer. Depending what you're then doing, the newline, if left, might affect the results. – Ceisc Aug 25 '17 at 21:54
  • 1
    Using an external process to trim the variable in the loop is rather inefficient. You can use the shell's [parameter expansion](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html) features instead. In Bash there is a facility for extracting a particular substring by index, or you can use the prefix and suffix substitutions with a pattern which matches the desired width, though it requires a bit of back-and-forth. – tripleee Nov 23 '18 at 05:32
10

If you want N digits, add 10^N and delete the first digit.

for (( num=100; num<=105; num++ ))
do
  echo ${num:1:3}
done

Output:

01
02
03
04
05
Meyer
  • 1,662
  • 7
  • 21
  • 20
6

Other way :

zeroos="000"
echo 

for num in {99..105};do
 echo ${zeroos:${#num}:${#zeroos}}${num}
done

So simple function to convert any number would be:

function leading_zero(){

    local num=$1
    local zeroos=00000
    echo ${zeroos:${#num}:${#zeroos}}${num} 

}
Paco
  • 131
  • 2
  • 5
  • if the number **must not** have more than 3 digits, i would recommend the first variant with the `temp_num` from https://askubuntu.com/a/1257316/354350. **||** although i personally prefer `echo ${zeroos:0:-${#num}}${num}` like mentioned in "**Note 1**". – DJCrashdummy Aug 27 '20 at 07:25
6

One way without using external process forking is string manipulation, in a generic case it would look like this:

#start value
CNT=1

for [whatever iterative loop, seq, cat, find...];do
   # number of 0s is at least the amount of decimals needed, simple concatenation
   TEMP="000000$CNT"
   # for example 6 digits zero padded, get the last 6 character of the string
   echo ${TEMP:(-6)}
   # increment, if the for loop doesn't provide the number directly
   TEMP=$(( TEMP + 1 ))
done

This works quite well on WSL as well, where forking is a really heavy operation. I had a 110000 files list, using printf "%06d" $NUM took over 1 minute, the solution above ran in about 1 second.

Tamisoft
  • 73
  • 1
  • 3
  • unfortunately my suggested edit to change the last line of the loop `TEMP=$(( TEMP + 1 ))` to `CNT=$(( CNT + 1 ))` got rejected, although increasing `TEMP` instead of `CNT` makes no sense! ...because `TEMP` gets overwritten with `0000001` at the beginning of the loop, you'll get an infinite loop. – DJCrashdummy Aug 25 '20 at 11:41
  • beyond that, this answer (especially `echo ${TEMP:(-6)}`) is perfectly fine! - although [there are also slightly different variants](https://askubuntu.com/a/1257316/354350) which may be a little bit more appropriate for different use cases. – DJCrashdummy Aug 25 '20 at 11:46
3

This will work also:

for i in {0..9}{0..9}{0..9}{0..9}
do
  echo "$i"
done
Chris
  • 61
  • 1
  • 6
3

If you're just after padding numbers with zeros to achieve fixed length, just add the nearest multiple of 10 eg. for 2 digits, add 10^2, then remove the first 1 before displaying output.

This solution works to pad/format single numbers of any length, or a whole sequence of numbers using a for loop.

# Padding 0s zeros:
# Pure bash without externals eg. awk, sed, seq, head, tail etc.
# works with echo, no need for printf

pad=100000      ;# 5 digit fixed

for i in {0..99999}; do ((j=pad+i))
    echo ${j#?}
done

Tested on Mac OSX 10.6.8, Bash ver 3.2.48

Zimba
  • 2,854
  • 18
  • 26
3

TL;DR

$ seq 1 10 | awk '{printf("%05d\n", $1)}'

Input(Pattern 1. Slow):

$ seq 1 10 | xargs -n 1 printf "%05d\n"

Input(Pattern 2. Fast):

$ seq 1 10 | awk '{printf("%05d\n", $1)}'

Output(same result in each case):

00001
00002
00003
00004
00005
00006
00007
00008
00009
00010

Explanation

I'd like to suggest the above patterns. These implementations can be used as a command so that we can use them again with ease. All you have to care about in these commands is the length of the numbers after being converted.(like changing the number %05d into %09d.) Plus, it's also applicable to other solutions such as the following. The example is too dependent on my environment, so your output might be different, but I think you can tell the usefulness easily.

$ wc -l * | awk '{printf("%05d\n", $1)}'
00007
00001
00001
00001
00013
00017
00001
00001
00001
00043

And like this:

$ wc -l * | awk '{printf("%05d\n", $1)}' | sort | uniq
00001
00007
00013
00017
00043

Moreover, if you write in this manner, we can also execute the commands asynchronously. (I found a nice article: https://www.dataart.com/en/blog/linux-pipes-tips-tricks)

disclaimer: I'm not sure of this, and I am not a *nix expert.

Performance test:

Super Slow:

$ time seq 1 1000 | xargs -n 1 printf "%09d\n" > test
seq 1 1000  0.00s user 0.00s system 48% cpu 0.008 total
xargs -n 1 printf "%09d\n" > test  1.14s user 2.17s system 84% cpu 3.929 total

Relatively Fast:

for i in {1..1000}
do
   printf "%09d\n" $i
done
$ time sh k.sh > test
sh k.sh > test  0.01s user 0.01s system 74% cpu 0.021 total


for i in {1..1000000}
do
   printf "%09d\n" $i
done
$ time sh k.sh > test
sh k.sh > test  7.10s user 1.52s system 99% cpu 8.669 total

Fast:

$ time seq 1 1000 | awk '{printf("%09d\n", $1)}' > test
seq 1 1000  0.00s user 0.00s system 47% cpu 0.008 total
awk '{printf("%09d\n", $1)}' > test  0.00s user 0.00s system 52% cpu 0.009 total


$ time seq 1 1000000 | awk '{printf("%09d\n", $1)}' > test
seq 1 1000000  0.27s user 0.00s system 28% cpu 0.927 total
awk '{printf("%09d\n", $1)}' > test  0.92s user 0.01s system 99% cpu 0.937 total

If you have to implement the higher performance solution, probably it may require other techniques, not using the shell script.

harukaeru
  • 683
  • 6
  • 15
1

Combining the seq -w answers with @tripleee 's comment on the question, you can do the following:

seq -w 1 1000 | while read -r i; do
    echo $i
done

As explained in the article @tripleee linked, this will read a single a single number from the pipe at a time, rather than loading the whole sequence into memory at once.

Note that I can't see any meaningful difference in speed compared to the for loop version, or the {0000..1000} version.

Can you avoid looping?

If you're doing this to run a command which can accept multiple arguments, it may be faster to run the command directly using multiple arguments and brace expansion than looping.

For example, on my system it takes 7 seconds to run the following loop:

for i in {0001..9001}; do; touch $i; done

But the following runs almost instantly:

touch {0001..9001}
daviewales
  • 2,144
  • 21
  • 30
0

1.) Create a sequence of numbers 'seq' from 1 to 1000, and fix the width '-w' (width is determined by length of ending number, in this case 4 digits for 1000).

2.) Also, select which numbers you want using 'sed -n' (in this case, we select numbers 1-100).

3.) 'echo' out each number. Numbers are stored in the variable 'i', accessed using the '$'.

Pros: This code is pretty clean.

Cons: 'seq' isn't native to all Linux systems (as I understand)

for i in `seq -w 1 1000 | sed -n '1,100p'`; 
do 
    echo $i; 
done
nick
  • 1
0

you don't need awk for that — either seq or jot alone suffices :

% seq -f '%05.f' 6     # bsd-seq
00001
00002
00003
00004
00005
00006

% gseq -f '%05.f' 6    # gnu-seq
00001
00002
00003
00004
00005
00006

% jot -w '%05.f' 6
00001
00002
00003
00004
00005
00006

…… unless you're going into bigint territory :

% gawk -Mbe '

  function __(_,___) {
      return +_<+___?___:_
  }
  BEGIN {
        _+=_^=_<_                 
      ____="%0*.f\n"   
  } {                      
       ___=__($--_, !+$++_)                
     _____=__(++_+--_, length(______=+$NF)) 
     do {                     
        printf(____,_____,___)
     }  while (___++<______) 
                                                       
  }' <<< '999999999999999999996 1000000000000000000003'

0999999999999999999996
0999999999999999999997
0999999999999999999998
0999999999999999999999
1000000000000000000000
1000000000000000000001
1000000000000000000002
1000000000000000000003

——————————————————————————————————————————————————

If you need to print out a HUGE range of numbers, then this approach maybe a bit faster -

  • printing out every integer from 1 to 1 million, left-zero-padded to 9-digits wide, in 0.049s

  • *caveat : I didn't have the spare time to make it cover all input ranges :: it's just a proof of concept accepting increments of powers of 10

——————————————————————————————————————————————————

 ( time ( LC_ALL=C mawk2 '
 
   function jot(____,_______,_____,_,__,___,______) {
       if(____==(____^!____)) {
           return +____<+_______\
               ? sprintf("%0*.f",_______,____)\
               : +____ 
        }
        _______= (_______-=____=length(____)-\
                 (_=!(_<_)))<+_ \
                 ? "" \
                 : sprintf("%0*.f",_______,!_)
           __=_= (!(__=_+=_+_))(__=(-+--_)+(__+=_)^++_)\
                 (__+=_=(((_--^_--+_++)^++_-_^!_)/_))(__+_)
          _____= "."
     gsub(_____,"\\&&",__)
     ____—-
     do { 
         gsub(_____,__,_)
        _____=_____"." 
     } while(—____)

     gsub(_____,(_______)"&\n",_)
     sub("^[^\n]+[\n]","",_)
     sub(".$",""~"",_______)
     
     return \
     (_)(_______)\
     sprintf("%0*.f",length(_____),__<__)

   } { print jot($1,$2) }' <<< '10000000 9'
  
 ) | pvE9 ) |xxh128sum |ggXy3 | lgp3

 sleep 2
 ( time ( LC_ALL=C jot 1000000 | 
          LC_ALL=C mawk2 '{ printf("%09.f\n", $1) }' 
 
 ) | pvE9 ) |xxh128sum |ggXy3 | lgp3


     out9: 9.54MiB 0:00:00 [ 275MiB/s] [ 275MiB/s] [<=> ]
( LC_ALL=C mawk2  <<< '1000000 9'; )

0.04s user 0.01s system 93% cpu 0.049 total

e0491043bdb4c8bc16769072f3b71f98  stdin


     out9: 9.54MiB 0:00:00 [36.5MiB/s] [36.5MiB/s] [  <=> ]
( LC_ALL=C jot 1000000 | LC_ALL=C mawk2 '{printf("%09.f\n", $1)}'; )

0.43s user 0.01s system 158% cpu 0.275 total

e0491043bdb4c8bc16769072f3b71f98  stdin

By the time you're doing 10 million, the time differences become noticeable :

 out9: 95.4MiB 0:00:00 [ 216MiB/s] [ 216MiB/s] [<=> ]
 ( LC_ALL=C mawk2  <<< '10000000 9'; )

 0.38s user 0.06s system 95% cpu 0.458 total

 be3ed6c8e9ee947e5ba4ce51af753663  stdin


 out9: 95.4MiB 0:00:02 [36.3MiB/s] [36.3MiB/s] [ <=> ]
 ( LC_ALL=C jot 10000000 | LC_ALL=C mawk2 '{printf("%09.f\n", $1)}'; )

 4.30s user 0.04s system 164% cpu 2.638 total
 
 be3ed6c8e9ee947e5ba4ce51af753663  stdin




 out9: 95.4MiB 0:00:02 [35.2MiB/s] [35.2MiB/s] [ <=> ]

 ( LC_ALL=C python3 -c '__=1; ___=10**7;

   [ print("{0:09d}".format(_)) for _ in range(__,___+__) ]' 

 ) | pvE9 ) | xxh128sum |ggXy3 | lgp3 ;  )

 2.68s user 0.04s system 99% cpu 2.725 total
 
 be3ed6c8e9ee947e5ba4ce51af753663  stdin
RARE Kpop Manifesto
  • 2,453
  • 3
  • 11