如何在 Bash 中重复一个字符?
Posted
技术标签:
【中文标题】如何在 Bash 中重复一个字符?【英文标题】:How can I repeat a character in Bash? 【发布时间】:2011-07-18 00:12:50 【问题描述】:我怎么能用echo
做到这一点?
perl -E 'say "=" x 100'
【问题讨论】:
很遗憾这不是 Bash。 不带回声,但在同一主题ruby -e 'puts "=" * 100'
或python -c 'print "=" * 100'
好问题。非常好的答案。我在这里的实际工作中使用了其中一个答案,我将作为示例发布:github.com/drbeco/oldfiles/blob/master/oldfiles(使用printf
和seq
)svrb=`printf '%.sv' $(seq $vrb)`
打印任何内容(1 个或多个字符,甚至包括换行符)的通用解决方案:Repeat_this () i=1;而 [ "$i" -le "$2" ];做 printf "%s" "$1"; i=$(( $i + 1 )) ;完毕 ; printf '\n' ; 。像这样使用:Repeat_this "something" Number_of_repetitions。例如,展示重复 5 次的内容,包括 3 个换行符: Repeat_this "$(printf '\n\n\nthis')" 5 。最后的 printf '\n' 可能会被取出(但我把它放进去创建文本文件,而那些需要换行符作为最后一个字符!)
【参考方案1】:
你可以使用:
printf '=%.0s' 1..100
这是如何工作的:
Bash 扩展 1..100,因此命令变为:
printf '=%.0s' 1 2 3 4 ... 100
我已将 printf 的格式设置为 =%.0s
,这意味着无论给出什么参数,它都将始终打印一个 =
。因此它打印 100 =
s。
【讨论】:
很好的解决方案,即使重复次数很大,也能很好地执行。这是一个函数包装器,您可以使用repl = 100
调用,例如(不幸的是,需要eval
技巧,以便将大括号扩展基于变量):repl() printf "$1"'%.s' $(eval "echo 1.."$(($2))"");
是否可以使用 var 设置上限?我已经尝试过,但无法正常工作。
您不能在大括号扩展中使用变量。改用seq
,例如$(seq 1 $limit)
.
如果您对其进行功能化,最好将其从$s%.0s
重新排列为%.0s$s
,否则破折号会导致printf
错误。
这让我注意到 Bash 的 printf
的一个行为:它继续应用格式字符串,直到没有剩下的参数。我以为它只处理了一次格式字符串!【参考方案2】:
不容易。但例如:
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)他们似乎不支持它:)
【讨论】:
请注意,带有 seq 的第一个选项打印的数字比给定的数字少一个,因此该示例将打印 99 个=
字符。
printf
tr
是唯一的 POSIX 解决方案,因为 seq
、yes
和 1..3
不是 POSIX。
重复一个字符串而不仅仅是一个字符:printf %100s | sed 's/ /abc/g'
- 输出 'abcabcabc...'
+1 用于不使用循环且仅使用一个外部命令 (tr
)。您也可以将其扩展为 printf "%$COLUMNSs\n" | tr " " "="
。
@CamiloMartin:感谢您的跟进:确实归结为seq
实现(因此隐含平台):GNU seq
(Linux)产生 1 个 =
比指定的数字(不像我最初声称的那样,但正如你已经正确确定的那样),而 BSD seq
(类似 BSD 的平台, 包括 OSX) 产生所需的数字。简单的测试命令:seq -s= 100 | tr -d '[:digit:]\n' | wc -c
BSD seq
将 =
放在 每个 数字之后,包括最后一个,而 GNU seq 放置一个 换行符在 last 数字之后,因此相对于 =
计数不足 1。【参考方案3】:
向@gniourf_gniourf 致谢。
注意:此答案不回答原始问题,但补充通过比较性能现有的、有用的答案。
解决方案仅在执行速度方面进行比较 - 不考虑内存要求(它们因解决方案而异,并且可能与大量重复次数有关)。 p>
总结:
如果您的重复次数小,比如说最多 100 次左右,值得使用 仅 Bash 的解决方案,因为外部实用程序的启动成本很重要,尤其是 Perl 的。 不过,务实地说,如果您只需要 一个 重复字符的实例,那么所有现有的解决方案都可以。 大量重复计数,使用外部实用程序,因为它们会更快。 尤其要避免使用大字符串替换 Bash 的全局子字符串 (例如,$var// /=
),因为它非常慢。
以下是在 2012 年末 iMac 上拍摄的时间,配备 3.2 GHz Intel Core i5 CPU 和 Fusion Drive,运行 OSX 10.10.4 和 bash 3.2.57,是平均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 的解决方案处于领先地位 - 但只有如此小的重复计数! (见下文)。
外部实用程序的启动成本在这里确实很重要,尤其是 Perl 的。如果您必须在循环中调用它 - 在每次迭代中使用 small 重复计数 - 避免使用 multi-utility、awk
和 perl
解决方案。
大量重复计数:1000000(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 解决方案是迄今为止最快的。
Bash 的全局字符串替换 ($foo// /=
) 在使用大字符串时速度非常慢,并且已经停止运行(在 Bash 4.3.30 中大约需要 50 分钟(!),在 Bash 3.2 中甚至更长。 57 - 我从不等它完成)。
Bash 循环很慢,算术循环 ((( i= 0; ... ))
) 比大括号扩展循环 (1..n
) 慢 - 尽管算术循环更节省内存。
awk
指的是 BSD awk
(也可以在 OSX 上找到) - 它明显比 gawk
(GNU Awk) 慢,尤其是 mawk
。
请注意,具有大量计数和多字符。字符串,内存消耗可以成为一个考虑因素 - 方法在这方面有所不同。
这是产生上述内容的 Bash 脚本 (testrepeat
)。
它需要 2 个参数:
换句话说:上面的时间是用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; 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_REPETITIONSs" | 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_REPETITIONSs" | 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_REPETITIONSs" '='; 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
【讨论】:
看时序比较很有趣,但我认为在许多程序中输出是缓冲的,所以如果缓冲关闭,它们的时序可以改变。In order to use brace expansion with a variable, we must use `eval`
?
所以 perl 解决方案 (sid_com) 基本上是最快的……一旦达到启动 perl 的初始开销。 (从 59 毫秒的小重复到 67 毫秒的一百万次重复......所以 perl 分叉在您的系统上花费了大约 59 毫秒)【参考方案4】:
有不止一种方法可以做到这一点。
使用循环:
大括号扩展可以与整数文字一起使用:
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 次。
使用head
(printf
等)和tr
:
head -c 100 < /dev/zero | tr '\0' '='
printf %100s | tr " " "="
【讨论】:
++ 用于head
/ tr
解决方案,即使重复次数很高也能很好地工作(小警告:head -c
不符合 POSIX,但 BSD 和 GNU head
实施它);虽然在这种情况下其他两种解决方案会很慢,但它们确实也具有处理 multi 字符串的优势。
使用 yes
和 head
-- 如果您想要一定数量的换行符,这很有用:yes "" | head -n 100
。 tr
可以让它打印任何字符:yes "" | head -n 100 | tr "\n" "="; echo
有点意外:dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/null
比head -c100000000 < /dev/zero | tr '\0' '=' >/dev/null
版本慢很多。当然你必须使用 100M+ 的块大小来合理测量时间差。 100M 字节分别需要 1.7 秒和 1 秒,其中显示了两个各自的版本。我取下 tr 并将其转储到 /dev/null
,head
版本为 0.287 秒,dd
版本为 0.675 秒,十亿字节。
对于:dd if=/dev/zero count=1 bs=100000000 | tr '\0' '=' >/dev/null
=> 0,21332 s, 469 MB/s
;对于:dd if=/dev/zero count=100 bs=1000000| tr '\0' '=' >/dev/null
=> 0,161579 s, 619 MB/s
;【参考方案5】:
我刚刚找到了一种使用 seq 的非常简单的方法:
更新:这适用于 OS X 附带的 BSD seq
。其他版本的 YMMV
seq -f "#" -s '' 10
将打印 '#' 10 次,如下所示:
##########
-f "#"
设置格式字符串以忽略数字并为每个数字打印#
。
-s ''
将分隔符设置为空字符串以删除 seq 在每个数字之间插入的换行符
-f
和 -s
之后的空格似乎很重要。
编辑:这是一个方便的功能......
repeat ()
seq -f $1 -s '' $2; echo
你可以这样称呼...
repeat "#" 10
注意:如果您重复#
,那么引号很重要!
【讨论】:
这给了我seq: format ‘#’ has no % directive
。 seq
用于数字,而不是字符串。见gnu.org/software/coreutils/manual/html_node/seq-invocation.html
啊,所以我使用的是在 OS X 上找到的 BSD 版本的 seq。我会更新答案。你用的是哪个版本?
我正在使用来自 GNU coreutils 的 seq。
@JohnB: BSD seq
在这里被巧妙地改用 来复制字符串:传递给-f
的格式字符串——通常用于格式化正在生成的 numbers - 仅包含要在此处复制的字符串,以便输出仅包含该字符串的副本。不幸的是,GNU seq
坚持在格式字符串中存在 数字格式,这就是您看到的错误。
干得好;也适用于 multi 字符串。请使用"$1"
(双引号),这样你也可以传入'*'
等字符和嵌入空格的字符串。最后,如果您希望能够使用%
,您必须将其加倍(否则seq
会认为它是格式规范的一部分,例如%f
);使用"$1//%/%%"
会解决这个问题。由于(正如您所提到的)您使用的是 BSD seq
,因此 通常可以在类似 BSD 的操作系统上运行(例如,FreeBSD) - 相比之下,它 无法在使用 GNU seq
的 Linux 上运行。【参考方案6】:
这里有两种有趣的方式:
ubuntu@ubuntu:~$ 是 = |头-10 |粘贴 -s -d '' - ========== ubuntu@ubuntu:~$ 是 = |头-10 | tr -d "\n" ==========ubuntu@ubuntu:~$请注意,这两者有细微的不同 - paste
方法以新行结束。 tr
方法没有。
【讨论】:
干得好;请注意,BSDpaste
莫名其妙地需要 -d '\0'
来指定一个空分隔符,并且由于 -d ''
而失败 - -d '\0'
应该适用于所有与 POSIX 兼容的 paste
实现,并且确实适用于GNU paste
也是。
本质上类似,但外部工具更少:yes | mapfile -n 100 -C 'printf = \#' -c 1
@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)
【参考方案7】:
没有简单的方法。避免使用 printf
和替换的循环。
str=$(printf "%40s")
echo $str// /rep
# echoes "rep" 40 times.
【讨论】:
不错,但只有在重复次数很少的情况下才能合理执行。这是一个函数包装器,可以作为repl = 100
调用,例如(不输出尾随\n
):repl() local ts=$(printf "%$2s"); printf %s "$ts// /$1";
@mklement0 很高兴您提供两种解决方案的功能版本,两者都 +1!
不涉及外部程序的绝佳解决方案。不过,我会使用printf -v str …
而不是str=$(printf …)
来避免调用子shell。对于一般的解决方案,我会使用printf "%s" "$str// /rep"
而不是echo
,因为printf
更健壮,并且不会像echo
那样阻塞以-
开头的字符串。【参考方案8】:
问题是关于如何使用echo
:
echo -e ''$_1..100'\b='
这将与perl -E 'say "=" x 100'
完全相同,但仅适用于echo
。
【讨论】:
现在这很不寻常,如果您没有在其中添加额外的空格退格.. 或使用以下命令清理它: echo -e $_1..100'\b=' |列 Bad idea. 如果$_1
、$_2
或一百个变量中的任何其他变量有值,这将失败。
@JohnKugelman echo $( set --; eval echo -e \$1..100'\\b=' )
这是恶心。我喜欢它:D【参考方案9】:
纯 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 varname
:printf
不会打印到标准输出,而是将格式化字符串的内容放在变量 varname
中。
'%*s':printf
将使用参数打印相应数量的空格。例如,printf '%*s' 42
将打印 42 个空格。
最后,当我们的变量中有所需的空格数时,我们使用参数扩展将所有空格替换为我们的模式:$var// /$pattern
将扩展为 var
的扩展,所有空格替换为$pattern
的扩展。
您还可以通过使用间接扩展来摆脱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 左右的重复计数(因此对于大多数现实生活中的应用程序来说可能很好,如果我猜的话),它得到对于更高的计数非常慢(请参阅下一条评论)。 看来bash
在参数扩展($var//old/new
)的上下文中的全局字符串替换操作特别慢:bash 3.2.57
慢到极点,bash 4.3.30
慢,至少在我的 3.2 Ghz Intel Core i5 机器上的 OSX 10.10.3 系统上:计数为 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
上快得多,但仍然很慢)。
【参考方案10】:
如果您希望在 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'
相同的输出。
【讨论】:
问题是seq
不是 POSIX 实用程序(尽管 BSD 和 Linux 系统有它的实现) - 您可以使用 while
循环来执行 POSIX shell 算术,就像在 @Xennex81 中一样回答(正如您正确建议的那样,使用printf "="
,而不是echo -n
)。
糟糕,你说的很对。这样的事情有时会从我身边溜走,因为这个标准没有任何意义。 cal
是 POSIX。 seq
不是。无论如何,我将添加一个 RYO 函数,而不是用 while 循环重写答案(正如你所说,这已经在其他答案中了)。这样更有教育意义;-)。【参考方案11】:
这是我在 linux 中用来在屏幕上打印一行字符的方法(基于终端/屏幕宽度)
在屏幕上打印“=”:
printf '=%.0s' $(seq 1 $(tput cols))
说明:
按给定序列打印等号的次数:
printf '=%.0s' #sequence
使用命令的输出(这是一个称为命令替换的 bash 功能):
$(example_command)
给出一个序列,我以 1 到 20 为例。在最终命令中,使用 tput 命令而不是 20:
seq 1 20
给出终端当前使用的列数:
tput cols
【讨论】:
【参考方案12】:#!/usr/bin/awk -f
BEGIN
OFS = "="
NF = 100
print
或者
#!/usr/bin/awk -f
BEGIN
while (z++ < 100) printf "="
Example
【讨论】:
干得好;这是 POSIX 兼容的,即使在重复次数很高的情况下也相当快,同时还支持多字符串。这是外壳版本: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
中的一个错误,其中传递一个以=
开头的变量值会破坏命令。)
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 ';
.
用户注意 - NF=100 技巧会导致旧 awk 出现段错误。 original-awk
是老版awk 在Linux 下的名称,类似于BSD 的awk,也有报崩溃,如果你想试试这个。请注意,崩溃通常是寻找可利用漏洞的第一步。这个答案是如此宣传不安全的代码。
用户注意 - original-awk
是非标准的,不推荐
第一个代码 sn-p 的替代方案可以是awk NF=100 OFS='=' <<< ""
(使用bash
和gawk
)【参考方案13】:
另一种意思是将任意字符串重复n次:
优点:
适用于 POSIX shell。 可以将输出分配给变量。 重复任何字符串。 即使重复次数非常多也非常快。缺点:
需要 Gnu Core Utils 的yes
命令。
#!/usr/bin/sh
to_repeat='='
repeat_count=80
yes "$to_repeat" | tr -d '\n' | head -c "$repeat_count"
使用 ANSI 终端和 US-ASCII 字符重复。您可以使用 ANSI CSI 转义序列。这是重复一个字符的最快方式。
#!/usr/bin/env bash
char='='
repeat_count=80
printf '%c\e[%db' "$char" "$repeat_count"
或静态:
打印一行80次=
:
printf '=\e[80b\n'
限制:
并非所有终端都能理解repeat_char
ANSI CSI 序列。
只能重复 US-ASCII 或单字节 ISO 字符。
在最后一列重复停止,因此无论终端宽度如何,您都可以使用较大的值来填充整行。
重复仅用于显示。将输出捕获到 shell 变量中不会将 repeat_char
ANSI CSI 序列扩展为重复字符。
【讨论】:
次要注意 - 如果终端处于环绕模式,REP (CSI b) 应该正常环绕。【参考方案14】:在 bash 3.0 或更高版本中
for i in 1..100;do echo -n =;done
【讨论】:
【参考方案15】:我猜这个问题的最初目的是仅使用 shell 的内置命令来执行此操作。所以for
循环和printf
s 是合法的,而rep
、perl
和下面的jot
则不是。不过,下面的命令
jot -s "/" -b "\\" $((COLUMNS/2))
例如,打印一个窗口范围的行 \/\/\/\/\/\/\/\/\/\/\/\/
【讨论】:
干得好;即使重复次数很高(同时也支持多字符串),这也能很好地工作。为了更好地说明该方法,以下是 OP 命令的等效项:jot -s '' -b '=' 100
。需要注意的是,虽然类似 BSD 的平台(包括 OSX)带有 jot
,但 Linux 发行版没有。
谢谢,我更喜欢你对 -s '' 的使用。我已经更改了我的脚本。
在最近的基于 Debian 的系统上,apt install athena-jot
将提供 jot
。【参考方案16】:
正如其他人所说,在 bash 中 brace expansion 在 parameter expansion 之前,所以 <em>m</em>,<em>n</em>
范围只能包含文字。 seq
和 jot
提供了干净的解决方案,但不能从一个系统完全移植到另一个系统,即使您在每个系统上使用相同的 shell。 (尽管seq
越来越可用;例如,in FreeBSD 9.3 and higher。)eval
和其他形式的间接寻址总是有效,但有些不雅。
幸运的是,bash supports C-style for loops(仅限算术表达式)。所以这里有一个简洁的“纯 bash”方式:
repecho() for ((i=0; i<$1; ++i)); do echo -n "$2"; done; echo;
这将重复次数作为第一个参数,将要重复的字符串(可能是单个字符,如问题描述)作为第二个参数。 repecho 7 b
输出 bbbbbbb
(由换行符终止)。
Dennis Williamson 将essentially this solution four years ago in his excellent answer 给Creating string of repeated characters in shell script。我的函数体与那里的代码略有不同:
由于这里的重点是重复单个字符并且 shell 是 bash,因此使用 echo
而不是 printf
可能是安全的。我阅读了这个问题中的问题描述,表达了使用echo
打印的偏好。上面的函数定义适用于 bash 和 ksh93。虽然printf
更便携(通常应该用于这类事情),但echo
的语法可以说更具可读性。
一些 shell 的 echo
内置函数将 -
本身解释为一个选项——尽管 -
的通常含义,使用标准输入作为输入,对于 echo
来说是无意义的。 zsh 这样做。并且肯定存在无法识别-n
的echo
s,如it is not standard。 (许多 Bourne 风格的 shell 根本不接受 C 风格的 for 循环,因此不需要考虑它们的 echo
行为..)
这里的任务是打印序列; there,就是给变量赋值。
如果$n
是所需的重复次数并且您不必重复使用它,并且您想要更短的内容:
while ((n--)); do echo -n "$s"; done; echo
n
必须是一个变量——这种方式不适用于位置参数。 $s
是要重复的文本。
【讨论】:
强烈避免循环版本。printf "%100s" | tr ' ' '='
是最佳选择。
良好的背景信息和将功能打包为函数的荣誉,顺便说一句,它也适用于zsh
。 echo-in-a-loop 方法适用于较小的重复计数,但对于较大的重复计数,有基于 utilities 的符合 POSIX 的替代方案,@Slomojo 的评论证明了这一点。
在较短的循环周围添加括号可以保留 n 的值而不影响回声:(while ((n--)); do echo -n "$s"; done; echo)
使用 printf 代替 echo!它更便携(echo -n 只能在某些系统上工作)。请参阅unix.stackexchange.com/questions/65803/…(Stephane Chazelas 的精彩回答之一)
@OlivierDulac 这里的问题是关于 bash。无论您运行什么操作系统,如果您在其上使用 bash,bash 都有一个支持 -n
的 echo
内置函数。你所说的精神是绝对正确的。 printf
几乎总是比echo
更受欢迎,至少在非交互式使用中是这样。但我不认为对一个要求提供一个并且提供足够信息以知道它会起作用的问题给出echo
的答案有任何不恰当或误导性。另请注意,POSIX 本身不保证对((n--))
(没有$
)的支持。【参考方案17】:
Python 无处不在,而且在任何地方都一样。
python -c "import sys; print('*' * int(sys.argv[1]))" "=" 100
字符和计数作为单独的参数传递。
【讨论】:
我认为这是python -c "import sys; print(sys.argv[1] * int(sys.argv[2]))" "=" 100
的意图
@loevborg 是不是有点牵强?【参考方案18】:
for i in 1..100
do
echo -n '='
done
echo
【讨论】:
【参考方案19】:如果你想重复一个字符 n 次 n VARIABLE 次数取决于你可以做的字符串的长度:
#!/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========
【讨论】:
length
不能与expr
一起使用,您可能是指n=$(expr 10 - $#vari)
;然而,使用 Bash 的算术扩展更简单、更高效:n=$(( 10 - $#vari ))
。此外,您的答案的核心是 OP 正在寻找 Bash alternative 的 Perl 方法。【参考方案20】:
repeat()
# $1=number of patterns to repeat
# $2=pattern
printf -v "TEMP" '%*s' "$1"
echo $TEMP// /$2
【讨论】:
【参考方案21】:这是 Eliah Kagan 所支持的加长版:
while [ $(( i-- )) -gt 0 ]; do echo -n " "; done
当然,你也可以使用 printf,但不是我喜欢的:
printf "%$(( i*2 ))s"
此版本兼容 Dash:
until [ $(( i=i-1 )) -lt 0 ]; do echo -n " "; done
i 是初始数字。
【讨论】:
在 bash 中,n:while (( i-- )); do echo -n " "; done
有效。【参考方案22】:
最简单的就是在 csh/tcsh 中使用这个单行代码:
printf "%50s\n" '' | tr '[:blank:]' '[=]'
【讨论】:
或 bash as printf "%50s\n" " "|tr ' ' "="【参考方案23】:建议的 Python 解决方案的一个更优雅的替代方案可能是:
python -c 'print "="*(1000)'
【讨论】:
【参考方案24】:另一种选择是使用 GNU seq 并删除它生成的所有数字和换行符:
seq -f'#%.0f' 100 | tr -d '\n0123456789'
此命令打印 #
字符 100 次。
【讨论】:
不需要 .f:echo $(seq -f'#' 100 | tr -d '\n')
【参考方案25】:
不是堆积,而是另一种纯 Bash 方法利用 $//
替换数组:
$ arr=(1..100)
$ printf '%s' "$arr[@]/*/="
====================================================================================================
【讨论】:
【参考方案26】:另一个使用 printf 和 tr 的 bash 解决方案
注意。在我开始之前:
我们需要另一个答案吗? 可能不会。 这个答案已经在这里了吗? 看不到,就这样吧。使用printf
的前导零填充功能并使用tr
转换零。这避免了任何1..N
生成器:
$ printf '%040s' | tr '0' '='
========================================
对于较大的 N,这比生成器的性能要好得多;在我的机器上(bash 3.2.57):
$ time printf '=%.0s' 1..1000000 real: 0m2.580s
$ time printf '%01000000s' | tr '0' '=' real: 0m0.577s
【讨论】:
【参考方案27】:function repeatString()
local -r string="$1"
local -r numberToRepeat="$2"
if [[ "$string" != '' && "$numberToRepeat" =~ ^[1-9][0-9]*$ ]]
then
local -r result="$(printf "%$numberToRepeats")"
echo -e "$result// /$string"
fi
样本运行
$ repeatString 'a1' 10
a1a1a1a1a1a1a1a1a1a1
$ repeatString 'a1' 0
$ repeatString '' 10
参考库位于:https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash
【讨论】:
【参考方案28】:我怎么能用 echo 做到这一点?
如果echo
后跟sed
,您可以使用echo
执行此操作:
echo | sed -r ':a s/^(.*)$/=\1/; /^=100$/q; ba'
实际上,echo
在那里是不必要的。
【讨论】:
【参考方案29】:我的答案有点复杂,而且可能并不完美,但对于那些希望输出大量数字的人来说,我能够在 3 秒内完成大约 1000 万次。
repeatString()
# argument 1: The string to print
# argument 2: The number of times to print
stringToPrint=$1
length=$2
# Find the largest integer value of x in 2^x=(number of times to repeat) using logarithms
power=`echo "l($length)/l(2)" | bc -l`
power=`echo "scale=0; $power/1" | bc`
# Get the difference between the length and 2^x
diff=`echo "$length - 2^$power" | bc`
# Double the string length to the power of x
for i in `seq "$power"`; do
stringToPrint="$stringToPrint$stringToPrint"
done
#Since we know that the string is now at least bigger than half the total, grab however many more we need and add it to the string.
stringToPrint="$stringToPrint$stringToPrint:0:$diff"
echo $stringToPrint
【讨论】:
【参考方案30】:最简单的就是在 bash 中使用这个单行代码:
seq 10 | xargs -n 1 | xargs -I echo -n ===\>;echo
【讨论】:
以上是关于如何在 Bash 中重复一个字符?的主要内容,如果未能解决你的问题,请参考以下文章