I'd like to output hello world over 20 characters.

printf "%-20s :\n\n" 'hello world!!'

# Actual output
hello world!!        :

# Wanted output
hello world!!========:

However, I don't want to complete with spaces but with "=" instead. How do I do that?


printf '%s\n' "$string${filler:${#string}}"



${#string} is the length of the value $string, and ${filler:${#string}} is the substring of $filler from offset ${#string} onwards.

The total width of the output will be that of the maximum width of $filler or $string.

The filler string can, on systems that has jot, be created dynamically using

filler=$( jot -s '' -c 16 '=' '=' )

(for 16 = in a line). GNU systems may use seq:

filler=$( seq -s '=' 1 16 | tr -dc '=' )

Other systems may use Perl or some other faster way of creating the string dynamically.

  • @sddgob How would you do it with printf + brace expansion in bash?
    – Kusalananda
    Oct 13 '18 at 7:39
printf "%.20s:\n\n" "$str========================="

where %.20s is the string truncating format

  • 2
    This is IMHO, a better solution than mine. It only needs a short explanation of the format string.
    – Kusalananda
    Oct 18 '17 at 12:45

One way to do it:

printf "====================:\r%s\n\n" 'hello world!!'
  • 6
    Ha! That's a clever trick! However, it will actually print ====================\rhello world, which might be an issue if the OP needs to store this and not just print it to screen.
    – terdon
    Oct 18 '17 at 10:04
  • also echo -e '=================\rHello World!!', but has same issue as @terdon pointed that. Oct 19 '17 at 8:08
  • 1
    @αғsнιη Only if echo supports -e. printf is almost always better than echo, for many reasons. Oct 19 '17 at 8:11

Updated answer to be more general solution. see also my another answer below using only shell brace expansion and printf.

$ str='Hello World!'
$ sed -r ':loop; s/ (=*):$/\1=:/; t loop' <<< "$(printf '%-20s:\n' "$str" )"
Hello World!========:

How it works?

this (=*):$/ captures one space, one-or-more = that followed by a colon : in the end of its input; we make the set of = as a group match and \1 will be its back-reference.

With :loop we defined a label named loop and with t loop it will jump to that label when a s/ (=*):$/\1=:/ has done successful substitution;

In replacement part with \1=:, it will always increment the number of =s and back the colon itself to the end of string.

  • 1
    so you have to adjust the flag depending on how many words the input has?
    – user1686
    Oct 19 '17 at 6:37
  • @grawity I have updated my answer to general solution now. Sep 25 '18 at 12:29

A Perl approach:

$ perl -le '$k="hello world!!"; while(length($k)<20){$k.="=";} print "$k\n"'
hello world!!=======

Or, better, @SatoKatsura pointed out in the comments:

perl -le '$k = "hello world!!"; print $k, "=" x (20-length $k), "\n"'

If you need to support UTF multi-byte characters, use:

PERL_UNICODE='AS' perl -le '$k = "hello world!!"; print $k, "=" x (20-length $k), "\n"'

Same idea in the shell:

v='hello world!!'; while [ ${#v} -lt 20 ]; do v="$v""="; done; printf '%s\n\n' "$v"
  • You don't need a loop: perl -le '$k = "hello world!!"; print $k, "=" x (20-length $k), "\n"'. However, this (and all other solutions posted so far) breaks if multi-byte characters are involved. Oct 18 '17 at 10:45
  • @SatōKatsura ooh, yes, that's neat! Should have thought of that, thanks. And yes, I was thinking of adding a disclaimer for possible failure on UTF multi-byte characters, but figured it would be a needless complication in this context.
    – terdon
    Oct 18 '17 at 11:03
  • I think perl6 might have a way to do it correctly even with multi-byte characters. But on the other hand perl6 is annoying in so many ways. Oct 18 '17 at 11:06
  • @SatōKatsura well, for this sort of simple thing, it should be enough to just set PERL_UNICODE='AS'. For example: printf '%s' nóóös | perl -nle 'print length($_)' prints 8 ("wrong") while printf '%s' nóóös | PERL_UNICODE='AS' perl -nle 'print length($_)' prints 5 ("correct").
    – terdon
    Oct 18 '17 at 11:23

Another way is using only printf command and generate the character padding pattern first by Shell Brace Expansion (You can put end with a number ≥ formatting area you want to print in {1..end}) and get only every first character of it %.1s which is =s and then print only first 20 characters length area of that %.20s. This is kind of better way to having repeated characters/word instead of duplicating them.

printf '%.20s:\n' "$str$(printf '%.1s' ={1..20})"
Hello World!!=======:


Normally as Brace Expansion, shell expanding {1..20} as following if we print those.

printf '%s ' {1..20}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 

So with adding an equal sign to it ={1..20}, shell will expand as following.

printf '%s ' ={1..20}
=1 =2 =3 =4 =5 =6 =7 =8 =9 =10 =11 =12 =13 =14 =15 =16 =17 =18 =19 =20 

And with printf '%.1s' which is actually means printf '%WIDE.LENGTH', we are printing only one LENGTH of those at above with default 1 WIDE. so will result =s only and 20 times repeated itself.

Now with printf '%.20s:\n' we are printing only the 20 length of $str and if length of $str<20, the rest will take from generated =s to fill with instead of spaces.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.