我怎样才能在bash中重复一个字符?
我怎么能用echo
来做到这一点?
perl -E 'say "=" x 100'
您可以使用:
printf '=%.0s' {1..100}
这是如何工作的:
Bash展开{1..100},命令变成:
printf '=%.0s' 1 2 3 4 ... 100
我已经将printf的格式设置为=%.0s
,这意味着它将始终打印单个=
不pipe给定的参数。 因此它打印100 =
s。
没有简单的方法。 但是例如:
seq -s= 100|tr -d '[:digit:]'
或者,也许是符合标准的方式:
printf %100s |tr " " "="
还有一个tput rep
,但是对于我手头的terminal(xterm和linux),他们似乎不支持它:)
有不止一种方法来做到这一点。
使用循环:
-
Brace扩展可以使用整数文字:
for i in {1..100}; do echo -n =; done
-
类似C的循环允许使用variables:
start=1 end=100 for ((i=$start; i<=$end; i++)); do echo -n =; done
使用printf
内build:
printf '=%.0s' {1..100}
在此处指定精度将截断string以适合指定的宽度( 0
)。 由于printf
重用了格式化string以消耗所有的参数,这只是打印"="
100次。
使用head
( printf
等)和tr
:
head -c 100 < /dev/zero | tr '\0' '=' printf %100s | tr " " "="
给他@gniourf_gniourf的帽子提示。
注意:这个答案并不回答原来的问题,而是通过比较performance来 补充现有的,有用的答案。
仅在执行速度方面比较解决scheme – 不考虑内存要求(它们在不同的解决scheme中有所不同,可能与大量的重复计数有关)。
概要:
- 如果你的重复计数很小 ,比如高达100左右,那么使用Bash-only解决scheme是值得的 ,因为外部实用程序的启动成本很重要,特别是Perl的。
- 然而,从语用上来说,如果你只需要一个重复字符的实例,那么所有现有的解决scheme都可能没问题。
- 有了大量的重复计数 , 使用外部工具 ,因为它们会更快。
- 特别是避免使用大stringreplaceBash的全局子string
(例如${var// /=}
),因为它太慢了。
- 特别是避免使用大stringreplaceBash的全局子string
以下是采用3.2 GHz Intel Core i5 CPU和Fusion Drive的2012年末iMac的时间表,运行OSX 10.10.4和bash 3.2.57,是1000次运行的平均值。
这些条目是:
- 按执行时间的升序列出(最快的第一个)
- 前缀为:
-
M
…一个潜在的多字符解决scheme -
S
…一个单一字符的解决scheme -
P
…符合POSIX标准的解决scheme
-
- 随后简要介绍解决scheme
- 后缀为原始答案的作者姓名
- 小重复计数: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
- 只有Bash解决scheme领先 – 但只有重复计数这个小! (见下文)。
- 外部实用程序的启动成本在这里确实很重要,特别是Perl的。 如果你必须循环调用它 – 在每次迭代中都要重复一次,避免使用多实用程序,
awk
和perl
解决scheme。
- 大重复次数:100万(100万)
[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
- 这个问题的Perl解决scheme是迄今为止最快的。
- Bash的全局stringreplace(
${foo// /=}
)对于大string是莫名其妙的慢,而且已经被删除了(在Bash 4.3.30中花了大约50分钟(!),在Bash中花了更长的时间3.2.57 – 我从来没有等待它完成)。 - Bash循环很慢,算术循环(
(( i= 0; ... ))
)比括号扩展的慢({1..n}
) – 虽然算术循环更具有内存效率。 -
awk
指的是BSDawk
(在OSX上也是这样) – 它明显比gawk
(GNU Awk)慢,尤其是mawk
。 - 请注意,大计数和多字符。 string,内存消耗可以成为一个考虑因素 – 方法在这方面有所不同。
这是产生上述的Bash脚本 ( testrepeat
)。 它需要2个参数:
- 字符重复计数
- 可选地,执行testing运行的次数并计算来自的平均时间
换句话说:上面的时间是用testrepeat 100 1000
和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; eg, 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 -vt "%${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
我刚刚发现了一个很简单的方法来使用seq来做到这一点:
更新:这适用于与OS X. YMMV与其他版本自带的BSD seq
seq -f "#" -s '' 10
将打印“#”10次,如下所示:
##########
-
-f "#"
将格式化string设置为忽略数字,并为每个数字打印#
。 -
-s ''
将分隔符设置为空string以删除seq在每个数字之间插入的换行符 -
-f
和-s
之后的空格似乎很重要。
编辑:这是在一个方便的function…
repeat () { seq -f $1 -s '' $2; echo }
你可以这样调用…
repeat "#" 10
注意:如果你重复#
那么报价是重要的!
这里有两个有趣的方法:
ubuntu @ ubuntu:〜$ yes = | 头-10 | 粘贴-s -d'' - ========== ubuntu @ ubuntu:〜$ yes = | 头-10 | tr -d“\ n” ========== Ubuntu的@ Ubuntu的:〜$
请注意,这两个细微差别 – paste
方法结束在一个新的行。 tr
方法不。
没有简单的方法。 避免使用printf
和replace循环。
str=$(printf "%40s") echo ${str// /rep} # echoes "rep" 40 times.
#!/usr/bin/awk -f BEGIN { OFS = "=" NF = 100 print }
要么
#!/usr/bin/awk -f BEGIN { while (z++ < 100) printf "=" }
例
我想这个问题的最初目的就是用shell的内置命令来完成这个工作。 所以for
循环和printf
将是合法的,而rep
, perl
和下面也jot
下不会。 还有,下面的命令
jot -s "/" -b "\\" $((COLUMNS/2))
例如打印一个窗口宽度的\/\/\/\/\/\/\/\/\/\/\/\/
正如其他人所说,在bash 括号扩展之前参数扩展 ,所以{ m , n }
范围只能包含文字。 seq
和jot
提供了干净的解决scheme,但是不能从一个系统完全移植到另一个系统,即使你在每个系统上使用相同的shell。 (尽pipeseq
越来越多,例如, 在FreeBSD 9.3及更高版本中 ) eval
和其他forms的间接方式总能正常工作,但有点不雅观。
幸运的是,bash 支持C风格的循环 (只有算术expression式)。 所以这里有一个简洁的“纯粹的打击”方式:
repecho() { for ((i=0; i<$1; ++i)); do echo -n "$2"; done; echo; }
这将重复次数作为第一个参数,并将需要重复的string(可能是单个字符,如问题描述中)作为第二个参数。 repecho 7 b
输出bbbbbbb
(以换行符结尾)。
丹尼斯·威廉姆森 ( Dennis Williamson)在四年前给出了基本的解决scheme,他 在shell脚本中创build重复string的 出色答案 。 我的函数体与这里的代码略有不同:
-
由于这里的重点在于重复单个字符,并且shell是bash,所以使用
echo
而不是printf
可能是安全的。 我在这个问题中读到了问题描述,expression了用echo
打印的偏好。 上面的函数定义在bash和ksh93中工作 。 尽pipeprintf
更具可移植性(通常应该用于这类事情),但echo
的语法可以说是更可读的。一些shell的
echo
内置解释-
作为一个选项本身 – 即使通常的含义-
使用stdininput,对echo
是无意义的。 zsh做到这一点。 而且确实存在不认识的echo
-n
,因为它不是标准的 。 (许多Bourne风格的shell根本不接受C风格的循环,因此不需要考虑它们的echo
行为。) -
这里的任务是打印序列; 那里 ,它是分配给一个variables。
如果$n
是所需的重复次数,并且不需要重复使用,并且您希望更短的内容:
while ((n--)); do echo -n "$s"; done; echo
n
必须是一个variables – 这种方式不适用于位置参数。 $s
是要重复的文本。
如果你希望在不同的echo
和printf
实现和/或除了bash
其他shell实现POSIX一致性和一致性:
seq(){ n=$1; while [ $n -le $2 ]; do echo $n; n=$((n+1)); done ;} # If you don't have it. echo $(for each in $(seq 1 100); do printf "="; done)
…会产生与perl -E 'say "=" x 100'
相同的输出, perl -E 'say "=" x 100'
。
在bash 3.0或更高
for i in {1..100};do echo -n =;done
for i in {1..100} do echo -n '=' done echo
repeat() { # $1=number of patterns to repeat # $2=pattern printf -v "TEMP" '%*s' "$1" echo ${TEMP// /$2} }
一个纯粹的Bash方式没有eval
,没有子壳,没有外部工具,没有大括号扩展(也就是说,你可以让数字在一个variables中重复):
如果给定一个variablesn
,该variables扩展为(非负数)和variablespattern
,例如,
$ n=5 $ pattern=hello $ printf -v output '%*s' "$n" $ output=${output// /$pattern} $ echo "$output" hellohellohellohellohello
你可以使用这个function:
repeat() { # $1=number of patterns to repeat # $2=pattern # $3=output variable name local tmp printf -v tmp '%*s' "$1" printf -v "$3" '%s' "${tmp// /$2}" }
有了这一套:
$ repeat 5 hello output $ echo "$output" hellohellohellohellohello
对于这个小窍门,我们使用printf
有很多:
-
-v varname
:printf
不是打印到标准输出,而是将格式化的string的内容放入variablesvarname
。 - '%* s':
printf
将使用参数来打印相应数量的空格。 例如,printf '%*s' 42
将打印42个空格。 - 最后,当我们在variables中有所需的空格数时,我们用一个参数扩展来replace我们模式中的所有空格:
${var// /$pattern}
将扩展到var
的扩展,所有的空格被replace为$pattern
的扩展。
您也可以使用间接扩展来删除repeat
函数中的tmp
variables:
repeat() { # $1=number of patterns to repeat # $2=pattern # $3=output variable name printf -v "$3" '%*s' "$1" printf -v "$3" '%s' "${!3// /$2}" }
如果你想重复一个字符n次,那么你可以做一个string的长度,例如:
#!/bin/bash vari='AB' n=$(expr 10 - length $vari) echo 'vari equals.............................: '$vari echo 'Up to 10 positions I must fill with.....: '$n' equal signs' echo $vari$(perl -E 'say "=" x '$n)
它显示:
vari equals.............................: AB Up to 10 positions I must fill with.....: 8 equal signs AB========
这是埃利亚·卡根所支持的更长的版本:
while [ $(( i-- )) -gt 0 ]; do echo -n " "; done
当然,你也可以使用printf,但是并不是我喜欢的:
printf "%$(( i*2 ))s"
这个版本是Dash兼容的:
until [ $(( i=i-1 )) -lt 0 ]; do echo -n " "; done
我是最初的号码。