79

Is there a way to print same character repeatedly in bash, just like you can use this construct to do this in python:

print('%' * 3)

gives

%%%
Ankur Agarwal
  • 23,692
  • 41
  • 137
  • 208
  • shortest + [fast](https://stackoverflow.com/a/30288267) + max compatibility `printf %9s|tr ' ' '\n'` –  Nov 14 '19 at 16:44

9 Answers9

190

There's actually a one-liner that can do this:

    printf "%0.s-" {1..10}

prints

    ----------

Here's the breakdown of the arguments passed to printf:

  • %s - This specifies a string of any length
  • %0s - This specifies a string of zero length, but if the argument is longer it will print the whole thing
  • %0.s - This is the same as above, but the period tells printf to truncate the string if it's longer than the specified length, which is zero
  • {1..10} - This is a brace expansion that actually passes the arguments "1 2 3 4 5 6 7 8 9 10"
  • "-" - This is an extra character provided to printf, it could be anything (for a "%" you must escape it with another "%" first, i.e. "%%")
  • Lastly, The default behavior for printf if you give it more arguments than there are specified in the format string is to loop back to the beginning of the format string and run it again.

The end result of what's going on here then is that you're telling printf that you want it to print a zero-length string with no extra characters if the string provided is longer than zero. Then after this zero-length string print a "-" (or any other set of characters). Then you provide it 10 arguments, so it prints 10 zero-length strings following each with a "-".

It's a one-liner that prints any number of repeating characters!

Edit:

Coincidentally, if you want to print $variable characters you just have to change the argument slightly to use seq rather than brace expansion as follows:

    printf '%0.s-' $(seq 1 $variable)

This will instead pass arguments "1 2 3 4 ... $variable" to printf, printing precisely $variable instances of "-"

CaffeineConnoisseur
  • 3,635
  • 4
  • 19
  • 16
  • 7
    Very good but this does not work in ***#!/bin/sh*** but only in ***#!/bin/bash*** – Daniele Vrut Oct 02 '13 at 09:00
  • 2
    Since this uses a bash built-in, for small numbers of repeated characters it should be faster than methods the spawn external processes. For large numbers, `printf '%*s' 1000000 ''|tr ' ' '-'` and other methods are faster. – Deadcode Feb 26 '14 at 17:13
  • My only question is in what situation would you want to print a repeating character 1 million times? Other operations I could see, but that would just saturate your output with seemingly meaningless characters. – CaffeineConnoisseur Feb 27 '14 at 19:09
  • 2
    Came across this question - and many other similar ones - because I needed to create a wordlist containing 1,000,000 words for performance testing purposes in a password-cracking program. – Hashim Aziz Feb 12 '17 at 23:00
  • 1
    For the record, I went with the "slow" one-liner solution of `printf "%0.s-" {1..1000000}` and it took around 5 seconds. – Hashim Aziz Feb 12 '17 at 23:09
  • You have lost me somewhere. How, exactly, does this print the same character 3 times? What does `{1..10}` have to do with printing something 3 times? – jww Sep 18 '17 at 02:40
  • 1
    @jww `{1..10}` prints 10 times as an example, 3 times would be `{1..3}`, and if you wanted to print `$n` number of times, where `$n = 3` you could do `printf '%0.s-' $(seq 1 $n)` – CaffeineConnoisseur Sep 18 '17 at 19:42
  • It's not necessary to pass `1` to `seq`, it starts there by default. – Azor Ahai -him- Oct 17 '17 at 23:36
  • 1
    Slight correction: the `0` is a width specifier, which specifies the minimum length. You then give an empty precision specifier `.` (as opposed to `.#`), which specifies a *maximum* width of 0. You can hence write `printf '%.s-'`, as the width specifier is redundant. – Qualia Dec 12 '18 at 23:47
  • Works great for printing, but doesn't work great to generate a string inside of a function. See @Deadcode 's answer below. – PeterK Mar 12 '19 at 13:41
58

sure, just use printf and a bit of bash string manipulation

$ s=$(printf "%-30s" "*")
$ echo "${s// /*}"
******************************

There should be a shorter way, but currently that's how i would do it. You can make this into a function which you can store in a library for future use

printf_new() {
 str=$1
 num=$2
 v=$(printf "%-${num}s" "$str")
 echo "${v// /*}"
}

Test run:

$ printf_new "*" 20
********************
$ printf_new "*" 10
**********
$ printf_new "%" 10
%%%%%%%%%%
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • 5
    You can avoid the subshell here if you use `printf -v`. `printf -v str '%-30s' ' '; echo "${str// /*}"` – kojiro Jun 20 '12 at 15:20
  • 2
    -1. This is very slow, especially for >1000 characters. Almost anything is faster, including `printf '%.s*' {1..30}`, or `printf '%*s' 1000000 ''|tr ' ' '*'` for large numbers. – Deadcode Feb 26 '14 at 17:17
  • 2
    The printf_new fails on the third test run. I would fix it like this: `printf -v v "%-*s" "$num" ""; echo "${v// /$str}"` – jarno Aug 28 '15 at 21:22
  • 4
    realization of such trivial things in bash makes me cry. – ipeacocks Jan 29 '17 at 23:06
34

The current accepted answer for this question (by ghostdog74) provides a method that executes extremely slowly for even a moderately high number of characters. The top-voted answer (by CaffeineConnoisseur) is better, but is still quite slow.

Here is what, in my tests, has executed fastest of all (even faster than the python version):

perl -E "print '*' x 1000"

In second place was the python command:

python -c "print('*' * 1000)"

If neither perl nor python are available, then this is third-best:

head -c 1000 /dev/zero | tr '\0' '*'

And in fourth place is the one using the bash printf built-in along with tr:

printf '%*s' 1000 | tr ' ' '*'

And here's one (from CaffeineConnoisseur's answer) that's in fifth place in speed for large numbers of repeated characters, but might be faster for small numbers (due to using only a bash built-in, and not spawning an external process):

printf '%.s*' {1..1000}
Community
  • 1
  • 1
Deadcode
  • 860
  • 9
  • 15
  • Assuming `*` is the character being outputted, what is the purpose of the preceding `%.s` in the final example? – Hashim Aziz Feb 12 '17 at 23:02
  • "%s" is a format specification for printf, simply meaning string. In between the "%" and "s", you can set length attributes for the string. Using either "." or ".0" will set the maximum string length to zero, printing nothing for that string. In this example, the range is expanded to 1 2 3 4 5 ..... 98 99 100 and each number is being treated as a zero-length string. This is how to control the count of asterisks printed. – user.friendly Sep 20 '17 at 04:34
  • To transfer the repeated string to a string variable use: `str=\`perl -E "print '*' x 1000"\`` – PeterK Mar 12 '19 at 13:44
15

I like this:

echo $(yes % | head -n3)

You may not like this:

for ((i=0; i<3; i++)){
   echo -ne "%"
}

You might like this:

s=$( printf "%3s" ); echo " ${s// /%}"

Source: http://dbaspot.com/shell/357767-bash-fast-way-repeat-string.html

There is also this form, but not very useful:

echo %{,,}
manojlds
  • 290,304
  • 63
  • 469
  • 417
5

It's ugly, but you can do it like this:

$ for a in `seq 5`; do echo -n %; done
%%%%%

Of course, seq is an external program (which you probably have).

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 2
    Why use another program when you can simply write: `for a in {1..5}; do echo -n %; done` .) – Daniele Vrut Oct 02 '13 at 09:06
  • 1
    I figured out my self that in a script, my "solution" doesn't work, here's an up for your solution. – Daniele Vrut Oct 02 '13 at 09:21
  • -1. Is slower than `printf "%0.s%%" {1..5}`, which is in turn slower than `printf '%*s' 5 ''|tr ' ' '%'`. – Deadcode Feb 26 '14 at 17:08
  • 5
    @Deadcode -1's are usually reserved for incorrect answers, not inefficient answers. There may be other ways to complete this operation, however this is a perfectly valid way... – War10ck Feb 26 '14 at 20:08
3
  1. It is possible to obtain any number of zero (\0) character from /dev/zero. The only thing left to do it is

    head -c 20 /dev/zero |tr '\0' '+'
    

    Note that head and tr are external commands. So that it would be invoked in separate process.

  2. It is possible to "optimize" this solution with string caching.

    CACHE="$(head -c 1000 /dev/zero |tr '\0' '+')"
    echo "${CACHE:0:10}"
    echo "${CACHE:0:100}"
    echo "${CACHE:0:300}"
    

    There are only bash built-ins in the echo statement. So, we can use it in cycle.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
ssvda
  • 91
  • 1
2

Another one:

char='%'
count=5
result=$( printf "%${count}s" ' ' )
echo -e ${result// /$char}
Fritz G. Mehner
  • 16,550
  • 2
  • 34
  • 41
1
#!/usr/bin/awk -f
BEGIN {
  while (c++ < 3) printf "%"
}

Result

%%%
Zombo
  • 1
  • 62
  • 391
  • 407
0

Here is the most simple way to do that

seq -s % 4|tr -d '[:digit:]'

Note there will be only 3 '%' in created sequence.

HovoK
  • 41
  • 5
  • -1. Prints a leading newline, prints one less character than the number passed, and is slower than `printf '%*s' 3 ''|tr ' ' '%'`. – Deadcode Feb 26 '14 at 17:01