Add leading characters in front of string using printf or echo

20,936

Solution 1

If you can use perl:

$ perl -e 'print sprintf "%020s\n","shortstring"'
000000000shortstring

For more general:

$ perl -e 'print sprintf "%020s\n",shift' shortstring
000000000shortstring

$ perl -e 'print sprintf "%020s\n", $_ for @ARGV' shortstring qwerty
000000000shortstring
00000000000000qwerty

Note

Some platform can be formated output string with leading zero, at least in AIX. With GNU gcc, you will have a warning when you compile:

$ cat test.c
#include <stdio.h>

int main() {
    printf("%08s\n", "qwerty");
    return 0;
}

Then:

$ gcc -Wall test.c
test.c: In function ‘main’:
test.c:4:5: warning: '0' flag used with ‘%s’ gnu_printf format [-Wformat]

If you don't want to use perl, you can try:

$ line=shortstring
$ printf "%0$((20-${#line}))d%s"  0  $line
000000000shortstring

This approach can not handle when string length greater than 20. You can use @mikeserv answer to handle that or using a check before printf.

Solution 2

Just do the padding manually. I don't think there's a more straightforward solution if you stick to POSIX tools.

while [ ${#line} -lt 20 ]; do
  line=0$line
done

or

n=${#line}
while [ $n -lt 20 ]; do
  printf '0'
  n=$((n-1))
done
printf "$line\n"

In zsh, of course, there's syntax for that. Unlike the snippets above, this one truncates the string if it's longer than 20 characters. If you don't want truncation, you can append the tail of the string.

print -r ${(l:20::0:)line}
print -r ${(l:20::0:)line}${line:20}

Solution 3

As @Gnouc has already noted, you're halfway there already:

line='some chars'
printf '%0'$((20-${#line}))'d%s\n' 0 "$line"

###OUTPUT###
0000000000some chars

Of course, then you still wind up with a 0 even if line is longer than 20 chars, and that's only if printf doesn't fail with an error because it doesn't properly handle the -dash in its format string (which is apparently the more likely case). Still, that is easily handled:

printf "%.$((((l=20-${#line})>0)*l))d%s\n" 0 "$line"

That format string uses printf's %.precision flag to either zero pad its decimal argument to the indicated length or - in the event of a %.0d null or %.d empty precision - to otherwise truncate any leading 0s to the number indicated. Like this:

printf '#%.0d%c%.5d' 0000000 ':separator' 4 
#:00004

(note: the %c format prints the first character of its associated argument)

The $((arithmetic expression)) first sets the var $l to 20 minus ${#line} then evaluates to 0 or 1 if $l is less than or greater than 0 respectively, and last multiplies that by $l. So when 20 - ${#line} is less than zero you do $((0*l)) but when it is greater you do $((1*l)).

In this way you always print only as many leading zeroes as are counted in the difference between 20 and ${#line}. Like this:

line='some chars'
printf "#%.$((((l=20-${#line})>0)*l))d%s\n" 0 "$line"
#0000000000some chars

And...

line='some chars'
printf "#%.$((((l=4-${#line})>0)*l))d%s\n" 0 "$line"
#some chars

And you can use the same %.precision form for the s type field as well, like:

line='some chars'
printf "#%.$((((l=4-${#line})>0)*l))d%.4s\n" 0 "$line"
#some

...for truncation as desired.

Here it is in a loop - the following iterates 5 times:

line=
until line=fivec$line; [ $((l)) -lt 0 ] 
do  printf "#%.$((((l=20-${#line})>0)*l))d%s\n" 0 "$line"
done    
#000000000000000fivec
#0000000000fivecfivec
#00000fivecfivecfivec
#fivecfivecfivecfivec
#fivecfivecfivecfivecfivec

It's important to note that none of the zeroes shown are ever included in the value of any shell variable at all - let alone $line - and are only generated automatically by printf as indicated by its format string.

Here's the same loop with an added precision %.20s:

line=
until line=fivec$line; [ $((l)) -lt 0 ] 
do  printf "#%.$((((l=20-${#line})>0)*l))d%.20s\n" 0 "$line"
done    
#000000000000000fivec
#0000000000fivecfivec
#00000fivecfivecfivec
#fivecfivecfivecfivec
#fivecfivecfivecfivec

Another thing you can do with this is pretty fun. When you combine printf's capability of dynamically generating 0's with $IFS you can have any string you want as many times as you want, like:

IFS=0 ; printf '10 lines\n%s' $(printf %010d) ; unset IFS 

###OUTPUT###
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines

Solution 4

%s is a string formatting specification. Zero padding is only defined to exist for numeric conversions:

0 For d, i, o, u, x, X, a, A, e, E, f, F, g, and G conversion specifiers, leading zeros (following any indication of sign or base) are used to pad to the field width ... For other conversions, the behavior is undefined.

If your $line is a number, use:

printf '%020i\n' $line

to get the result you want (or one of the other numeric format specifiers). If your value is more complicated but starts with a number, you can use:

printf '%020i %s\n' $num $restofline

to combine a number and a string together at once.

Solution 5

A separate printf can do the 0 padding with %0Xd when needed.

zero_pad(){
  # zero_pad <string> <length>
  [ ${#1} -lt $2 ] && printf "%0$(($2-${#1}))d" ''
  printf "%s\n" "$1"
}

.

$ zero_pad "" 5
00000

$ zero_pad "over" 5
0over

$ zero_pad "under" 4
under

$ zero_pad "exact" 5
exact

$ zero_pad " space" 7
0 space
Share:
20,936

Related videos on Youtube

polym
Author by

polym

[email protected]

Updated on September 18, 2022

Comments

  • polym
    polym over 1 year

    How can I add leading characters to fill a string to a certain length?

    Assume I want to add zeroes in front of a string, if said string is shorter than 20 characters:

    printf '%020s\n' "$line"
    

    However, this fills the string with leading whitespaces, but not anything else e.g. zeroes.

    The string can be a random string containing numbers and other characters.

    I don't want to split the string into number + rest for printf '%020i' $number or printf '%020d $number'.

    My current output is:

             shortstring
    

    My desired output is:

    000000000shortstring
    
  • polym
    polym almost 10 years
    Ok that looks good. Is it possible to give this perl script a set environment variable? I need perl to evaluate $line.
  • cuonglm
    cuonglm almost 10 years
    @polym: see my updated answer.
  • polym
    polym almost 10 years
    Cool thanks, it worked! :) That is real command line magic haha.
  • mikeserv
    mikeserv almost 10 years
    Thanks, man. Sorry again for not getting to the bottom of your answer before posting my own, but I just suck so bad at perl that reading through it hurt my feelings...