Add leading characters in front of string using printf or echo

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}

%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.


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