如何在Bash中重复一个字符?

338

我该如何使用 echo 来实现这个?

perl -E 'say "=" x 100'

很遗憾,这不是Bash。 - solidsnack
2
不要回声,但主题相同ruby -e'puts"="*100'python -c'print"="*100' - Evgeny
4
好的问题。非常好的答案。我在真实工作中使用了其中一个答案,并将其发布为示例:https://github.com/drbeco/oldfiles/blob/master/oldfiles (使用printfseq实现) svrb = \printf'%.sv' $(seq $vrb)`` - DrBeco
一个通用的解决方案,可以打印任何(1个或多个字符,甚至包括换行符):Repeat_this () { i=1; while [ "$i" -le "$2" ]; do printf "%s" "$1"; i=$(( $i + 1 )) ; done ; printf '\n' ;}。使用方法如下:Repeat_this "something" Number_of_repetitions。例如,为了展示重复5次某些内容,包括3个换行符:Repeat_this "$(printf '\n\n\nthis')" 5。最后的printf '\n'可以省略(但我加上它是为了创建文本文件,这些文件需要一个换行符作为它们的最后一个字符!) - Olivier Dulac
使用 Perl 对我来说已经足够好了。尝试了几个答案,但它们都在字符串末尾有一个 %,不知道为什么。 - Deqing
好的,我刚刚找到了原因,应该是 Perl -E 'print "=" x 100' - Deqing
39个回答

543

你可以使用:

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

工作原理:

Bash扩展{1..100},所以命令变成了:

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

我将printf的格式设置为=%.0s,这意味着无论给出什么参数,它都将始终打印一个单独的=。 因此,它会打印100个=


20
这是一个优秀的解决方案,即使在大量重复次数时也能表现良好。以下是一个函数包装器示例,您可以使用repl = 100调用它(不幸的是需要进行“eval”技巧,以便基于变量进行括号扩展):repl() { printf "$1"'%.s' $(eval "echo {1.."$(($2))"}"); } - mklement0
9
可以使用变量来设置上限吗?我尝试过了但无法让它工作。 - Mike Purcell
87
在大括号展开中不能使用变量,需要使用 seq 命令代替,例如 $(seq 1 $limit) - dogbane
18
如果你对此进行功能化处理,最好将其从$s%.0s重新排列为%.0s$s,否则破折号会导致printf出错。 - KomodoDave
8
这让我注意到Bash的printf的一个行为:它会一直应用格式字符串,直到没有参数为止。我以为它只处理一次格式字符串! - Jeenu
显示剩余15条评论

130

没有简单的方法。但是举个例子:

seq -s= 100|tr -d '[:digit:]'
# Editor's note: This requires BSD seq, and breaks with GNU seq (see comments)

或者可能是符合标准的方式:

printf %100s |tr " " "="

还有一个 tput rep,但是对于我手头的终端(xterm和linux),它们似乎不支持它 :)


4
请注意,使用seq命令的第一个选项会输出比给定数字少1,因此该示例将打印99个“=”字符。 - Camilo Martin
18
printftr 是唯一符合 POSIX 标准的解决方案,因为 seqyes{1..3} 不符合 POSIX 标准。 - Ciro Santilli OurBigBook.com
4
使用以下命令可以重复字符串而不只是单个字符:printf %100s | sed 's/ /abc/g' - 输出 'abcabcabc...'。 - John Rix
5
只用一个外部命令(tr),不使用循环语句实现加1操作。你也可以将它扩展到类似printf "%${COLUMNS}s\n" | tr " " "="这样的内容。 - musiphil
2
@CamiloMartin:感谢您的回复,这确实取决于 seq 的实现(因此也隐含了平台):GNU seq (Linux) 会比指定的数字少一个 =(与我最初所述的不同,但正如您正确地确定的那样),而 BSD seq (包括OSX在内的类似BSD的平台) 会产生所需的数字。简单的测试命令:seq -s= 100 | tr -d '[:digit:]\n' | wc -c。BSD seq 在_每个_数字后面放置=,包括最后一个数字,而GNU seq在最后一个数字后面放置_换行符_,因此在=计数方面短1。 - mklement0
显示剩余7条评论

82

向@gniourf_gniourf致敬,感谢他的贡献。

注意:本答案并不回答原问题,而是通过“比较性能”来补充现有的有用答案。

解决方案仅从执行速度方面进行比较 - 不考虑内存需求(它们在解决方案之间变化,并且可能会影响大量重复计数)。

摘要:

  • 如果您的重复计数,例如最多约100次,则值得使用仅Bash的解决方案,因为外部实用程序的启动成本很重要,特别是Perl的成本。
    • 实际上,如果您只需要一个重复字符的实例,则所有现有解决方案都可以。
  • 对于大量的重复计数,请使用外部实用程序,因为它们将更快。
    • 特别是,避免使用大型字符串的Bash全局子字符串替换
      (例如,${var// /=}),因为它的速度非常慢。

以下是在运行OSX 10.10.4和bash 3.2.57的3.2 GHz英特尔Core i5 CPU和Fusion Drive上进行的时间测量,平均值为1000次运行。

条目如下:

  • 按执行持续时间升序列出(最快的在前面)
  • 前缀为:
    • M ... 可能是多个字符解决方案
    • S ... 仅限单个字符的解决方案
    • P ... 符合POSIX标准的解决方案
  • 后跟解决方案的简要说明
  • 以原始答案的作者名称结尾

  • 小型重复计数: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-only解决方案领先于其他方案!(见下文)
  • 外部工具的启动成本在这里确实很重要,尤其是Perl。如果你必须在循环中调用它,并且每次迭代都有少量的重复次数,请避免使用多工具的awk和perl解决方案。

  • 大型重复次数:1000000(一百万)
[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的解决方案是最快的。
  • Bash的全局字符串替换(${foo// /=})对于大型字符串来说极其缓慢,且没有合理的解释,因此不能使用(在Bash 4.3.30中需要花费约50分钟(!),而在Bash 3.2.57中需要更长时间——我从未等待它完成)。
  • Bash循环速度较慢,算术循环((( i=0;...)))比括号扩展的循环({1..n})速度更慢——虽然算术循环更加节省内存。
  • awk指的是BSDawk(例如OSX上的),它显然比gawk(GNU Awk)和特别是mawk慢。
  • 请注意,在处理大量计数和多字符字符串时,内存消耗可能会成为一个考虑因素——这些方法在这方面有所不同。

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

  • 字符重复次数
  • 可选参数,测试运行次数和计算平均时间

换句话说:上述时间是通过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; 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

看时间比较确实很有趣,但我认为在许多程序中输出是缓冲的,所以如果关闭缓冲,它们的时间可以改变。 - Sergiy Kolodyazhnyy
In order to use brace expansion with a variable, we must use \eval`` - pyb
2
因此,Perl解决方案(sid_com)基本上是最快的...一旦达到启动Perl的初始开销。(对于小重复,它需要59毫秒,对于一百万次重复,它需要67毫秒...因此,在您的系统上,Perl分叉大约需要59毫秒) - Olivier Dulac
有趣的是,使用字符 * 时,我的当前目录文件列表的结果被获取到变量中,方法是执行以下操作:myvar=$(printf -- '*%.0s' {1..5}) - Ricky Levi
@RickyLevi,这只有在稍后的命令中使用 $myvar 未引用 的情况下才会发生,此时可预见地发生_路径扩展_ (globbing)。使用 echo "$myvar" 查看 $myvar 本身是否以逐字表明的 ``***** 填充正确。 - mklement0

61

有很多种方法可以实现。

使用循环:

  • 花括号扩展可以与整数文字一起使用:

for i in {1..100}; do echo -n =; done    
一个类C的循环允许使用变量:
start=1
end=100
for ((i=$start; i<=$end; i++)); do echo -n =; done

使用内置的printf函数:

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

在这里指定精度会截断字符串以适应指定的宽度(0)。由于printf重用格式字符串来消耗所有参数,因此这只是打印了"=" 100次。

使用headprintf等)和tr

head -c 100 < /dev/zero | tr '\0' '='
printf %100s | tr " " "="

3
对于head/tr的解决方案而言,即使重复次数很高也能够良好运行(小缺陷是:head -c不符合POSIX标准,但BSD和GNU的head都实现了它);而另外两种解决方案在这种情况下会比较慢,但它们也有一个优点,就是能够处理多字符字符串。 - mklement0
1
使用 yeshead -- 如果你想要一定数量的换行符,这个命令非常有用:yes "" | head -n 100tr 可以将任何字符打印出来:yes "" | head -n 100 | tr "\n" "="; echo - loxaxs
有点令人惊讶的是:dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/nullhead -c100000000 < /dev/zero | tr '\0' '=' >/dev/null 版本慢得多。当然,您必须使用100M+的块大小才能合理地测量时间差异。 100M字节需要1.7秒和1秒,分别显示了两个版本。我去掉了tr并将其转储到 /dev/null,对于十亿字节,head版本为0.287秒,而dd版本为0.675秒。 - Michael Goldshteyn
针对:dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/null => 0,21332 秒,469 MB/s;针对:dd if=/dev/zero count=100 bs=1000000| tr '\0' '=' >/dev/null => 0,161579 秒,619 MB/s; - 3ED
1
使用printf/tr打印标题和下划线: CAPTION="测试套件结果" && echo "${CAPTION}" && printf "%${#CAPTION}s\n" | tr " " "=" && echo 在Mac和Linux上可以很好地工作。 - Martyn Davis

38

我刚找到一个使用seq轻松完成此操作的方法:

更新:这在附带于 OS X 的 BSD seq 上可行。在其他版本中,请自行测试。

seq  -f "#" -s '' 10

将会打印 '#' 十次,如下所示:

##########
  • -f "#"将格式字符串设置为忽略数字,仅为每个数字打印#
  • -s''将分隔符设置为空字符串,以删除seq在每个数字之间插入的换行符
  • -f-s后面的空格似乎很重要。

编辑:这是一个方便的函数...

repeat () {
    seq  -f $1 -s '' $2; echo
}

你可以这样调用...

repeat "#" 10

注意: 如果您要重复使用 #,则引号很重要!


12
这让我出现了“seq:格式‘#’没有%指令”的错误提示。seq 是用于数字的,不是字符串。请参考 https://www.gnu.org/software/coreutils/manual/html_node/seq-invocation.html。 - John B
3
@JohnB: 这里BSD的seq被巧妙地重新用于复制“字符串”:传递给-f的格式字符串通常用于格式化生成的“数字”,但在这里仅包含要复制的字符串,以便输出仅包含该字符串的副本。不幸的是,GNU seq要求格式字符串中存在“数字格式”,这就是你看到的错误。 - mklement0
1
做得好,也适用于多字符字符串。请使用"$1"(双引号),这样您也可以传递诸如'*'和带有嵌入式空格的字符串。最后,如果您想要能够使用,则必须将其_加倍_(否则seq会认为它是格式规范的一部分,例如%f); 使用"${1//%/%%}"就可以解决这个问题。由于(如您所提到的)您正在使用_BSD_ seq,因此这将在_BSD_类似的操作系统上通常工作(例如FreeBSD)-相比之下,它在Linux上不起作用,其中使用_GNU_ seq - mklement0
在“-f”和“#”之间,空格是不必要的,因为选项参数是非空的。在“-s”之后是必需的,因为直接将空字符串与“-s”相连作为选项参数是技术上不可能的:shell会从字符串“-s''”中删除引号,然后将其传递给“seq”,所以“seq”只会看到“-s”。不仅“#”需要引用 - 所有所谓的shell元字符和“*”也需要引用;最好总是加上引号。 - mklement0
给我一个错误:seq: invalid format string: #'`。 - Ricky Levi
显示剩余3条评论

28

以下是两种有趣的方法:

ubuntu@ubuntu:~$ yes = | head -10 | paste -s -d '' -
==========
ubuntu@ubuntu:~$ yes = | head -10 | tr -d "\n"
==========ubuntu@ubuntu:~$ 

请注意这两种方法略有不同,paste 方法以一个新行结束,而 tr 方法不会。


2
做得很好;请注意,BSD paste 不可解释地需要 -d '\0' 来指定空分隔符,并且使用 -d '' 会失败 - -d '\0' 应该适用于所有符合 POSIX 标准的 paste 实现,实际上也可以在 GNU paste 中工作。 - mklement0
以更简洁的方式实现相似效果,减少外部工具的使用:yes | mapfile -n 100 -C 'printf = \#' -c 1 - bishop
@bishop:虽然您的命令确实创建了一个更少的子shell,但对于更高的重复次数,它仍然较慢,而对于较低的重复次数,差异可能并不重要;确切的阈值可能取决于硬件和操作系统,例如,在我的OSX 10.11.5机器上,这个答案在500时已经更快了;尝试 time yes = | head -500 | paste -s -d '\0' -; time yes | mapfile -n 500 -C 'printf = \#' -c 1。 然而,更重要的是:如果您已经使用printf,那么您可以采用更简单、更有效的方法,即接受的答案:printf '%.s=' $(seq 500) - mklement0

18

没有简单的方法。可以使用printf和替换来避免循环。

str=$(printf "%40s")
echo ${str// /rep}
# echoes "rep" 40 times.

2
不错,但只能在小的重复次数下表现得相对合理。这里有一个函数包装器,可以被调用为 repl = 100,例如(不输出尾随的 \n):repl() { local ts=$(printf "%${2}s"); printf %s "${ts// /$1}"; } - mklement0
1
@mklement0 很好的提供了两种解决方案的函数版本,两个都加一分! - Camilo Martin
1
一个不涉及外部程序的好解决方案。我会使用 printf -v str … 而不是 str=$(printf …) 来避免调用子shell。对于一个通用解决方案,我会使用 printf "%s" "${str// /rep}" 代替 echo,因为 printf 更加健壮,并且不像 echo 那样在以 - 开头的字符串上出现错误。 - musiphil

15

这个问题是关于如何使用 echo 来实现的:

echo -e ''$_{1..100}'\b='

这将完全像perl -E 'say "=" x 100'一样工作,但仅使用echo


现在这很不寻常,如果你不介意它里面有额外的空格和退格符号,或者可以使用以下命令清理它:echo -e $_{1..100}'\b=' | col - anthony
4
不好的想法。如果 $_1$_2 或其他上百个变量有值,这将失败。 - John Kugelman
2
@JohnKugelman 回声 $(set --; eval echo -e \ $ {{1..100}}'\ b =') - mug896
1
这真是恶心。我喜欢它:D - dimo414

12

一种纯Bash的方法,不使用eval、子shell、外部工具或花括号扩展(即,您可以将要重复的数字存储在一个变量中):

如果给定一个扩展为(非负)数字的变量n和一个变量pattern,例如:

$ n=5
$ pattern=hello
$ printf -v output '%*s' "$n"
$ output=${output// /$pattern}
$ echo "$output"
hellohellohellohellohello

你可以用以下代码创建一个函数:

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 变量名:使用该参数后,printf 命令将格式化后的字符串内容存储在变量 变量名 中,而非输出到标准输出流。
  • '%*s':使用该占位符,printf 命令将根据传入的参数打印相应数量的空格。例如,printf '%*s' 42 将打印出 42 个空格。
  • 最后,当我们在变量中得到了所需数量的空格后,我们使用参数扩展将所有空格替换为指定的模式:${变量// /$模式} 将被扩展为将 变量 中所有空格替换为 $模式 扩展后的字符串。

此外,您还可以通过间接扩展的方式,省去 repeat 函数中的 tmp 变量:

repeat() {
    # $1=number of patterns to repeat
    # $2=pattern
    # $3=output variable name
    printf -v "$3" '%*s' "$1"
    printf -v "$3" '%s' "${!3// /$2}"
}

传递变量名称的有趣变化方式。虽然对于重复计数高达1,000左右的情况,这种解决方案是可行的(如果我猜得没错的话),但对于更高的计数,它会变得非常缓慢(请参见下一个评论)。 - mklement0
似乎bash在参数扩展($ {var // old / new})的全局字符串替换操作方面特别慢:在我的OSX 10.10.3系统上,bash 3.2.57非常缓慢,而bash 4.3.30则较慢,至少在我的3.2 GHz英特尔Core i5机器上。当计数为1,000时,速度很慢(3.2.57)/快(4.3.30):0.1 / 0.004秒。将计数增加到10,000会产生截然不同的数字:repeat 10000 = var在bash 3.2.57中需要大约80秒!在bash 4.3.30中只需要大约0.3秒(比3.2.57快得多,但仍然很慢)。 - mklement0

10
#!/usr/bin/awk -f
BEGIN {
  OFS = "="
  NF = 100
  print
}

或者

#!/usr/bin/awk -f
BEGIN {
  while (z++ < 100) printf "="
}

示例


3
做得很好!这是符合POSIX标准且即使在高重复次数下速度也相当快,同时还支持多字符字符串。以下是Shell版本:awk 'BEGIN { while (c++ < 100) printf "=" }'。 将其封装成参数化的Shell函数后,可以通过 repeat 100 = 调用:repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { txt=substr(txt, 2); while (i++ < count) printf txt }'; }。 (虚拟前缀字符“.”和补充的substr调用是为了解决BSD awk中的一个错误,即传递以=开头的变量值会破坏命令的问题。) - mklement0
1
“NF = 100” 的解决方案非常聪明(尽管要得到 100 个“=”,必须使用“NF = 101”)。需要注意的是,它会使 BSD 的 awk 崩溃(但在 gawk 中速度非常快,在 mawk 中甚至更快),而且 POSIX 既不讨论将值赋给 NF,也不讨论在 BEGIN 块中使用字段。您可以通过微调来使其在 BSD 的 awk 中正常工作:“awk 'BEGIN { OFS = "="; $101=""; print }'”(但奇怪的是,在 BSD 的 awk 中,这并不比循环解决方案更快)。作为一个参数化的 shell 解决方案:“repeat() { awk -v count="$1" -v txt=".$2" 'BEGIN { OFS=substr(txt, 2); $(count+1)=""; print }'; }”。 - mklement0
注意:NF=100技巧会导致旧版awk出现段错误。original-awk是Linux下类似于BSD awk的旧版awk的名称,也有报道称其会崩溃。如果您想尝试,请注意崩溃通常是发现可利用漏洞的第一步。这个答案在促进不安全的代码。 - user2350426
2
注意:original-awk 是非标准的,不建议使用。 - Zombo
第一段代码的另一种替代方案是使用 bashgawk 的命令 awk NF=100 OFS='=' <<< "" - oliv
@user2350426:我认为这是因为原始的awk每行限制为99个字段(..这在很多情况下都很烦人:( ) - Olivier Dulac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接