How can I repeat a character in Bash? How can I repeat a character in Bash? bash bash

How can I repeat a character in Bash?


You can use:

printf '=%.0s' {1..100}

How this works:

Bash expands {1..100} so the command becomes:

printf '=%.0s' 1 2 3 4 ... 100

I've set printf's format to =%.0s which means that it will always print a single = no matter what argument it is given. Therefore it prints 100 =s.


No easy way. But for example:

seq -s= 100|tr -d '[:digit:]'

Or maybe a standard-conforming way:

printf %100s |tr " " "="

There's also a tput rep, but as for my terminals at hand (xterm and linux) they don't seem to support it:)


Tip of the hat to @gniourf_gniourf for his input.

Note: This answer does not answer the original question, but complements the existing, helpful answers by comparing performance.

Solutions are compared in terms of execution speed only - memory requirements are not taken into account (they vary across solutions and may matter with large repeat counts).

Summary:

  • If your repeat count is small, say up to around 100, it's worth going with the Bash-only solutions, as the startup cost of external utilities matters, especially Perl's.
    • Pragmatically speaking, however, if you only need one instance of repeating characters, all existing solutions may be fine.
  • With large repeat counts, use external utilities, as they'll be much faster.
    • In particular, avoid Bash's global substring replacement with large strings
      (e.g., ${var// /=}), as it is prohibitively slow.

The following are timings taken on a late-2012 iMac with a 3.2 GHz Intel Core i5 CPU and a Fusion Drive, running OSX 10.10.4 and bash 3.2.57, and are the average of 1000 runs.

The entries are:

  • listed in ascending order of execution duration (fastest first)
  • prefixed with:
    • M ... a potentially multi-character solution
    • S ... a single-character-only solution
    • P ... a POSIX-compliant solution
  • followed by a brief description of the solution
  • suffixed with the name of the author of the originating answer

  • Small repeat count: 100
[M, P] printf %.s= [dogbane]:                           0.0002[M   ] printf + bash global substr. replacement [Tim]:  0.0005[M   ] echo -n - brace expansion loop [eugene y]:       0.0007[M   ] echo -n - arithmetic loop [Eliah Kagan]:         0.0013[M   ] seq -f [Sam Salisbury]:                          0.0016[M   ] jot -b [Stefan Ludwig]:                          0.0016[M   ] awk - $(count+1)="=" [Steven Penny (variant)]:   0.0019[M, P] awk - while loop [Steven Penny]:                 0.0019[S   ] printf + tr [user332325]:                        0.0021[S   ] head + tr [eugene y]:                            0.0021[S, P] dd + tr [mklement0]:                             0.0021[M   ] printf + sed [user332325 (comment)]:             0.0021[M   ] mawk - $(count+1)="=" [Steven Penny (variant)]:  0.0025[M, P] mawk - while loop [Steven Penny]:                0.0026[M   ] gawk - $(count+1)="=" [Steven Penny (variant)]:  0.0028[M, P] gawk - while loop [Steven Penny]:                0.0028[M   ] yes + head + tr [Digital Trauma]:                0.0029[M   ] Perl [sid_com]:                                  0.0059
  • The Bash-only solutions lead the pack - but only with a repeat count this small! (see below).
  • Startup cost of external utilities does matter here, especially Perl's. If you must call this in a loop - with small repetition counts in each iteration - avoid the multi-utility, awk, and perl solutions.

  • Large repeat count: 1000000 (1 million)
[M   ] Perl [sid_com]:                                  0.0067[M   ] mawk - $(count+1)="=" [Steven Penny (variant)]:  0.0254[M   ] gawk - $(count+1)="=" [Steven Penny (variant)]:  0.0599[S   ] head + tr [eugene y]:                            0.1143[S, P] dd + tr [mklement0]:                             0.1144[S   ] printf + tr [user332325]:                        0.1164[M, P] mawk - while loop [Steven Penny]:                0.1434[M   ] seq -f [Sam Salisbury]:                          0.1452[M   ] jot -b [Stefan Ludwig]:                          0.1690[M   ] printf + sed [user332325 (comment)]:             0.1735[M   ] yes + head + tr [Digital Trauma]:                0.1883[M, P] gawk - while loop [Steven Penny]:                0.2493[M   ] awk - $(count+1)="=" [Steven Penny (variant)]:   0.2614[M, P] awk - while loop [Steven Penny]:                 0.3211[M, P] printf %.s= [dogbane]:                           2.4565[M   ] echo -n - brace expansion loop [eugene y]:       7.5877[M   ] echo -n - arithmetic loop [Eliah Kagan]:         13.5426[M   ] printf + bash global substr. replacement [Tim]:  n/a
  • The Perl solution from the question is by far the fastest.
  • Bash's global string-replacement (${foo// /=}) is inexplicably excruciatingly slow with large strings, and has been taken out of the running (took around 50 minutes(!) in Bash 4.3.30, and even longer in Bash 3.2.57 - I never waited for it to finish).
  • Bash loops are slow, and arithmetic loops ((( i= 0; ... ))) are slower than brace-expanded ones ({1..n}) - though arithmetic loops are more memory-efficient.
  • awk refers to BSD awk (as also found on OSX) - it's noticeably slower than gawk (GNU Awk) and especially mawk.
  • Note that with large counts and multi-char. strings, memory consumption can become a consideration - the approaches differ in that respect.

Here's the Bash script (testrepeat) that produced the above.It takes 2 arguments:

  • the character repeat count
  • optionally, the number of test runs to perform and to calculate the average timing from

In other words: the timings above were obtained with testrepeat 100 1000 and testrepeat 1000000 1000

#!/usr/bin/env bashtitle() { printf '%s:\t' "$1"; }TIMEFORMAT=$'%6Rs'# The number of repetitions of the input chars. to produceCOUNT_REPETITIONS=${1?Arguments: <charRepeatCount> [<testRunCount>]}# The number of test runs to perform to derive the average timing from.COUNT_RUNS=${2:-1}# Discard the (stdout) output generated by default.# If you want to check the results, replace '/dev/null' on the following# line with a prefix path to which a running index starting with 1 will# be appended for each test run; e.g., outFilePrefix='outfile', which# will produce outfile1, outfile2, ...outFilePrefix=/dev/null{  outFile=$outFilePrefix  ndx=0  title '[M, P] printf %.s= [dogbane]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  # !! In order to use brace expansion with a variable, we must use `eval`.  eval "  time for (( n = 0; n < COUNT_RUNS; n++ )); do     printf '%.s=' {1..$COUNT_REPETITIONS} >"$outFile"  done"  title '[M   ] echo -n - arithmetic loop [Eliah Kagan]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  time for (( n = 0; n < COUNT_RUNS; n++ )); do     for ((i=0; i<COUNT_REPETITIONS; ++i)); do echo -n =; done >"$outFile"  done  title '[M   ] echo -n - brace expansion loop [eugene y]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  # !! In order to use brace expansion with a variable, we must use `eval`.  eval "  time for (( n = 0; n < COUNT_RUNS; n++ )); do     for i in {1..$COUNT_REPETITIONS}; do echo -n =; done >"$outFile"  done  "  title '[M   ] printf + sed [user332325 (comment)]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  time for (( n = 0; n < COUNT_RUNS; n++ )); do     printf "%${COUNT_REPETITIONS}s" | sed 's/ /=/g' >"$outFile"  done  title '[S   ] printf + tr [user332325]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  time for (( n = 0; n < COUNT_RUNS; n++ )); do     printf "%${COUNT_REPETITIONS}s" | tr ' ' '='  >"$outFile"  done  title '[S   ] head + tr [eugene y]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  time for (( n = 0; n < COUNT_RUNS; n++ )); do     head -c $COUNT_REPETITIONS < /dev/zero | tr '\0' '=' >"$outFile"  done  title '[M   ] seq -f [Sam Salisbury]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  time for (( n = 0; n < COUNT_RUNS; n++ )); do     seq -f '=' -s '' $COUNT_REPETITIONS >"$outFile"  done  title '[M   ] jot -b [Stefan Ludwig]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  time for (( n = 0; n < COUNT_RUNS; n++ )); do     jot -s '' -b '=' $COUNT_REPETITIONS >"$outFile"  done  title '[M   ] yes + head + tr [Digital Trauma]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  time for (( n = 0; n < COUNT_RUNS; n++ )); do     yes = | head -$COUNT_REPETITIONS | tr -d '\n'  >"$outFile"  done  title '[M   ] Perl [sid_com]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  time for (( n = 0; n < COUNT_RUNS; n++ )); do     perl -e "print \"=\" x $COUNT_REPETITIONS" >"$outFile"  done  title '[S, P] dd + tr [mklement0]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  time for (( n = 0; n < COUNT_RUNS; n++ )); do     dd if=/dev/zero bs=$COUNT_REPETITIONS count=1 2>/dev/null | tr '\0' "=" >"$outFile"  done  # !! On OSX, awk is BSD awk, and mawk and gawk were installed later.  # !! On Linux systems, awk may refer to either mawk or gawk.  for awkBin in awk mawk gawk; do    if [[ -x $(command -v $awkBin) ]]; then      title "[M   ] $awkBin"' - $(count+1)="=" [Steven Penny (variant)]'      [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"      time for (( n = 0; n < COUNT_RUNS; n++ )); do         $awkBin -v count=$COUNT_REPETITIONS 'BEGIN { OFS="="; $(count+1)=""; print }' >"$outFile"      done      title "[M, P] $awkBin"' - while loop [Steven Penny]'      [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"      time for (( n = 0; n < COUNT_RUNS; n++ )); do         $awkBin -v count=$COUNT_REPETITIONS 'BEGIN { while (i++ < count) printf "=" }' >"$outFile"      done    fi  done  title '[M   ] printf + bash global substr. replacement [Tim]'  [[ $outFile != '/dev/null' ]] && outFile="$outFilePrefix$((++ndx))"  # !! In Bash 4.3.30 a single run with repeat count of 1 million took almost  # !! 50 *minutes*(!) to complete; n Bash 3.2.57 it's seemingly even slower -  # !! didn't wait for it to finish.  # !! Thus, this test is skipped for counts that are likely to be much slower  # !! than the other tests.  skip=0  [[ $BASH_VERSINFO -le 3 && COUNT_REPETITIONS -gt 1000 ]] && skip=1  [[ $BASH_VERSINFO -eq 4 && COUNT_REPETITIONS -gt 10000 ]] && skip=1  if (( skip )); then    echo 'n/a' >&2  else    time for (( n = 0; n < COUNT_RUNS; n++ )); do       { printf -v t "%${COUNT_REPETITIONS}s" '='; printf %s "${t// /=}"; } >"$outFile"    done  fi} 2>&1 |  sort -t$'\t' -k2,2n |    awk -F $'\t' -v count=$COUNT_RUNS '{     printf "%s\t", $1;     if ($2 ~ "^n/a") { print $2 } else { printf "%.4f\n", $2 / count }}' |     column -s$'\t' -t