14

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?

JJoao
  • 11,887
  • 1
  • 22
  • 44
smarber
  • 1,181
  • 2
  • 12
  • 25

6 Answers6

23
filler='===================='
string='foo'

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

Gives

foo=================

${#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.

Kusalananda
  • 320,670
  • 36
  • 633
  • 936
16
printf "%.20s:\n\n" "$str========================="

where %.20s is the string truncating format

JJoao
  • 11,887
  • 1
  • 22
  • 44
11

One way to do it:

printf "====================:\r%s\n\n" 'hello world!!'
Satō Katsura
  • 13,138
  • 2
  • 31
  • 48
  • 7
    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. – αғsнιη Oct 19 '17 at 08:08
  • 2
    @αғsнιη Only if `echo` supports `-e`. `printf` is almost always better than `echo`, for many reasons. – Satō Katsura Oct 19 '17 at 08:11
9

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.

αғsнιη
  • 40,939
  • 15
  • 71
  • 114
5

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"
terdon
  • 234,489
  • 66
  • 447
  • 667
  • 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. – Satō Katsura 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. – Satō Katsura 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
5

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!!=======:

Explanations:

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.

αғsнιη
  • 40,939
  • 15
  • 71
  • 114