我怎样才能在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次。

使用headprintf等)和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// /=} ),因为它太慢了。

以下是采用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的。 如果你必须循环调用它 – 在每次迭代中都要重复一次,避免使用多实用程序, awkperl解决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指的是BSD awk (在OSX上也是这样) – 它明显比gawk (GNU Awk)慢,尤其是mawk
  • 请注意,大计数和多字符。 string,内存消耗可以成为一个考虑因素 – 方法在这方面有所不同。

这是产生上述的Bash脚本testrepeat )。 它需要2个参数:

  • 字符重复计数
  • 可选地,执行testing运行的次数并计算来自的平均时间

换句话说:上面的时间是用testrepeat 100 1000testrepeat 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将是合法的,而repperl和下面也jot下不会。 还有,下面的命令

jot -s "/" -b "\\" $((COLUMNS/2))

例如打印一个窗口宽度的\/\/\/\/\/\/\/\/\/\/\/\/

正如其他人所说,在bash 括号扩展之前参数扩展 ,所以{ m , n }范围只能包含文字。 seqjot提供了干净的解决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是要重复的文本。

如果你希望在不同的echoprintf实现和/或除了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 varnameprintf不是打印到标准输出,而是将格式化的string的内容放入variablesvarname
  • '%* s': printf将使用参数来打印相应数量的空格。 例如, printf '%*s' 42将打印42个空格。
  • 最后,当我们在variables中有所需的空格数时,我们用一个参数扩展来replace我们模式中的所有空格: ${var// /$pattern}将扩展到var的扩展,所有的空格被replace为$pattern的扩展。

您也可以使用间接扩展来删除repeat函数中的tmpvariables:

 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 

我是最初的号码。