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
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.
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 BSDawk (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 bash
title() { printf '%s:\t' "$1"; }
TIMEFORMAT=$'%6Rs'
# The number of repetitions of the input chars. to produce
COUNT_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
As others have said, in bash brace expansion precedes parameter expansion, so {m,n} ranges can only contain literals. seq and jot provide clean solutions but aren't fully portable from one system to another, even if you're using the same shell on each. (Though seq is increasingly available; e.g., in FreeBSD 9.3 and higher.) eval and other forms of indirection always work but are somewhat inelegant.
Fortunately, bash supports C-style for loops (with arithmetic expressions only). So here's a concise "pure bash" way:
repecho() { for ((i=0; i<$1; ++i)); do echo -n "$2"; done; echo; }
This takes the number of repetitions as the first argument and the string to be repeated (which may be a single character, as in the problem description) as the second argument. repecho 7 b outputs bbbbbbb (terminated by a newline).
Since the focus here is on repeating a single character and the shell is bash, it's probably safe to use echo instead of printf. And I read the problem description in this question as expressing a preference to print with echo. The above function definition works in bash and ksh93. Although printf is more portable (and should usually be used for this sort of thing), echo's syntax is arguably more readable.
Some shells' echo builtins interpret - by itself as an option--even though the usual meaning of -, to use stdin for input, is nonsensical for echo. zsh does this. And there definitely exist echos that don't recognize -n, as it is not standard. (Many Bourne-style shells don't accept C-style for loops at all, thus their echo behavior needn't be considered..)
Here the task is to print the sequence; there, it was to assign it to a variable.
If $n is the desired number of repetitions and you don't have to reuse it, and you want something even shorter:
while ((n--)); do echo -n "$s"; done; echo
n must be a variable--this way doesn't work with positional parameters. $s is the text to be repeated.
For this little trick we're using printf quite a lot with:
-v varname: instead of printing to standard output, printf will put the content of the formatted string in variable varname.
'%*s': printf will use the argument to print the corresponding number of spaces. E.g., printf '%*s' 42 will print 42 spaces.
Finally, when we have the wanted number of spaces in our variable, we use a parameter expansion to replace all the spaces by our pattern: ${var// /$pattern} will expand to the expansion of var with all the spaces replaced by the expansion of $pattern.
You can also get rid of the tmp variable in the repeat function by using indirect expansion:
repeat() {
# $1=number of patterns to repeat
# $2=pattern
# $3=output variable name
printf -v "$3" '%*s' "$1"
printf -v "$3" '%s' "${!3// /$2}"
}
You can use:
How this works:
Bash expands {1..100} so the command becomes:
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.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:
(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:
M
... a potentially multi-character solutionS
... a single-character-only solutionP
... a POSIX-compliant solutionawk
, andperl
solutions.${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).(( i= 0; ... ))
) are slower than brace-expanded ones ({1..n}
) - though arithmetic loops are more memory-efficient.awk
refers to BSDawk
(as also found on OSX) - it's noticeably slower thangawk
(GNU Awk) and especiallymawk
.Here's the Bash script (
testrepeat
) that produced the above. It takes 2 arguments:In other words: the timings above were obtained with
testrepeat 100 1000
andtestrepeat 1000000 1000
There is no simple way. Avoid loops using
printf
and substitution.Or
Example
As others have said, in bash brace expansion precedes parameter expansion, so
{m,n}
ranges can only contain literals.seq
andjot
provide clean solutions but aren't fully portable from one system to another, even if you're using the same shell on each. (Thoughseq
is increasingly available; e.g., in FreeBSD 9.3 and higher.)eval
and other forms of indirection always work but are somewhat inelegant.Fortunately, bash supports C-style for loops (with arithmetic expressions only). So here's a concise "pure bash" way:
This takes the number of repetitions as the first argument and the string to be repeated (which may be a single character, as in the problem description) as the second argument.
repecho 7 b
outputsbbbbbbb
(terminated by a newline).Dennis Williamson gave essentially this solution four years ago in his excellent answer to Creating string of repeated characters in shell script. My function body differs slightly from the code there:
Since the focus here is on repeating a single character and the shell is bash, it's probably safe to use
echo
instead ofprintf
. And I read the problem description in this question as expressing a preference to print withecho
. The above function definition works in bash and ksh93. Althoughprintf
is more portable (and should usually be used for this sort of thing),echo
's syntax is arguably more readable.Some shells'
echo
builtins interpret-
by itself as an option--even though the usual meaning of-
, to use stdin for input, is nonsensical forecho
. zsh does this. And there definitely existecho
s that don't recognize-n
, as it is not standard. (Many Bourne-style shells don't accept C-style for loops at all, thus theirecho
behavior needn't be considered..)Here the task is to print the sequence; there, it was to assign it to a variable.
If
$n
is the desired number of repetitions and you don't have to reuse it, and you want something even shorter:n
must be a variable--this way doesn't work with positional parameters.$s
is the text to be repeated.A pure Bash way with no
eval
, no subshells, no external tools, no brace expansions (i.e., you can have the number to repeat in a variable):If you're given a variable
n
that expands to a (non-negative) number and a variablepattern
, e.g.,You can make a function with this:
With this set:
For this little trick we're using
printf
quite a lot with:-v varname
: instead of printing to standard output,printf
will put the content of the formatted string in variablevarname
.printf
will use the argument to print the corresponding number of spaces. E.g.,printf '%*s' 42
will print 42 spaces.${var// /$pattern}
will expand to the expansion ofvar
with all the spaces replaced by the expansion of$pattern
.You can also get rid of the
tmp
variable in therepeat
function by using indirect expansion: