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?

6 Answers 6

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.

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

where %.20s is the string truncating format

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

One way to do it:

printf "====================:\r%s\n\n" 'hello world!!'
3
  • 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
    Commented Oct 18, 2017 at 10:04
  • also echo -e '=================\rHello World!!', but has same issue as @terdon pointed that. Commented Oct 19, 2017 at 8:08
  • 2
    @αғsнιη Only if echo supports -e. printf is almost always better than echo, for many reasons. Commented Oct 19, 2017 at 8: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.

2
  • 1
    so you have to adjust the flag depending on how many words the input has? Commented Oct 19, 2017 at 6:37
  • @grawity I have updated my answer to general solution now. Commented Sep 25, 2018 at 12:29
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"
4
  • 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. Commented Oct 18, 2017 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
    Commented Oct 18, 2017 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. Commented Oct 18, 2017 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
    Commented Oct 18, 2017 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.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .