How to zero pad a sequence of integers in bash so that all have the same width?
BashNumbersPaddingBash Problem Overview
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
, 012312
and so forth.
Any idea on how i can do that?
Bash Solutions
Solution 1 - Bash
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
.
Solution 2 - Bash
Easier still you can just do
for i in {00001..99999}; do
echo $i
done
Solution 3 - Bash
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
produce
01
02
03
04
05
06
07
08
09
10
Solution 4 - Bash
use printf with "%05d" e.g.
printf "%05d" 1
Solution 5 - Bash
Very simple using printf
[jaypal:~/Temp] printf "%05d\n" 1
00001
[jaypal:~/Temp] printf "%05d\n" 2
00002
Solution 6 - Bash
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.
Solution 7 - Bash
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
Solution 8 - Bash
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
Solution 9 - Bash
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}
}
Solution 10 - Bash
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.
Solution 11 - Bash
This will work also:
for i in {0..9}{0..9}{0..9}{0..9}
do
echo "$i"
done
Solution 12 - Bash
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
Solution 13 - Bash
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
Solution 14 - Bash
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.
Solution 15 - Bash
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