在bash中,有更好的方法运行命令N次吗?

488

我偶尔会运行这样的一个bash命令行:


n=0; while [[ $n -lt 10 ]]; do some_command; n=$((n+1)); done

要连续运行some_command多次 - 在这种情况下为10次。

通常some_command是一系列命令或管道。

是否有更简洁的方法来做到这一点?


2
除了其他好的答案之外,您可以使用 let ++n 代替 n=$((n+1))(少3个字符)。 - musiphil
1
希望能提供一些关于哪些方法是可移植的指示。 - Faheem Mitha
1
http://serverfault.com/questions/273238/how-to-run-a-command-multiple-times - Ciro Santilli OurBigBook.com
9
如果您愿意更改shell,zshrepeat 10 do some_command; done。该命令可以重复执行某个命令10次。 - chepner
1
@JohnVandivier,我刚在一个新的18.04 docker容器中尝试了一下bash,在我的问题中发布的原始语法仍然有效。也许你正在运行其他类型的shell,例如/bin/sh(实际上是dash),那么就会出现sh:1:[[: not found错误提示。 - bstpierre
显示剩余3条评论
20个回答

753

如果您的范围中有一个变量,请像这样使用seq

count=10
for i in $(seq $count); do
    command
done

简单来说:

for run in {1..10}; do
  command
done

对于那些想要轻松复制和粘贴的人,可以将其简化为一行代码:

for run in {1..10}; do command; done

30
еҰӮжһңдҪ жңүеӨ§йҮҸзҡ„иҝӯд»ЈпјҢдҪҝз”Ёfor (n=0;n<k;n++)зҡ„еҪўејҸеҸҜиғҪжӣҙеҘҪпјӣжҲ‘жҖҖз–‘{1..k}дјҡз”ҹжҲҗдёҖдёӘеҢ…еҗ«жүҖжңүж•ҙ数并д»Ҙз©әж јеҲҶйҡ”зҡ„еӯ—з¬ҰдёІгҖӮ - Joe Koberg
12
在Bash中,花括号扩展形式不能轻松地使用变量来指定范围。 - Dennis Williamson
6
没问题。根据http://www.gnu.org/software/bash/manual/bashref.html#Brace-Expansion,大括号展开会在变量展开之前执行,因此它永远不会看到任何变量的值。这是正确的。 - Joe Koberg
5
关于变量扩展的令人悲伤的事情。 我正在使用以下命令循环n次并格式化数字: n=15; for i in $(seq -f "%02g" ${n}); do echo $i; done输出结果为: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 - user1830432
Ali Omary的答案(https://dev59.com/92865IYBdhLWcg3wl_o3#26898191)是唯一一个不使用外部工具或可能产生副作用的答案,因为“_”永远不应该用于存储持久数据。如果您将`run`用作变量,则在`for`循环之后会设置`run`。如果您在函数中,也可以在循环之前使用`local run`来避免覆盖可能在函数外设置的变量。 - Hart Simha
显示剩余4条评论

239

使用常量:

for ((n=0;n<10;n++)); do
    some_command; 
done

使用变量(可以包括数学表达式):

x=10; for ((n=0; n < (x / 2); n++)); do some_command; done

5
这是最接近编程语言的方法^^ - 应该被认可! - Nam G VU
这个更好,因为它只有一行代码,可以更方便地在终端中使用。 - Kunok
你也可以使用变量,例如 x=10 for ((n=0; n < $x; n++)); - Jelphy
2
@Kunok 可以将被接受的答案写成一行代码:for run in {1..10}; do echo "on run $run"; done,这同样是可以接受的。 - Douglas Adams
如果 n 已经设置为特定值怎么办?for (( ; n<10; n++ )) 不起作用 / 编辑:最好使用其他答案之一,比如 while (( n++... - phil294

167

另一个简单的方法来破解它:

seq 20 | xargs -Iz echo "Hi there"

执行echo命令20次。


请注意,seq 20 | xargs -Iz echo "Hi there z"输出如下:

Hi there 1
Hi there 2
...


30
2015版本:seq 20 | xargs -I{} echo "你好 {}" - mitnk
7
请注意,“z”不是一个好的占位符,因为它很常见,可能会在命令文本中作为普通文本出现。使用“{}”会更好。 - mitnk
4
有没有办法从 seq 中去掉数字?这样它就只会打印出“Hi there”20次(没有数字)? 编辑:只需使用 -I {},然后在命令中不再有任何 {} - Mike Graf
1
只需使用一些您确定不会出现在输出中的内容,例如 IIIII,这样看起来很好,例如:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs - rubo77

154

如果您使用的是zsh shell:

repeat 10 { echo 'Hello' }

其中 10 是命令将被重复执行的次数。


36
虽然这不是对问题的回答(因为它明确提到了bash),但它非常有用,帮助我解决了我的问题。+1 - OutOfBound
8
另外,如果您只是在终端中键入它,您可以使用类似于“repeat 10 { !! }”的东西。 - Renato Gomes
2
有没有办法在程序块中获取运行次数?例如:{ echo "运行次数 $n" } - Joshua Pinter
1
使用zsh的另一个绝佳理由。 - ruby_slinger

36

1
为什么要使用一个旨在并行运行事务的工具,其名称显然证明了这一点,然后再给它加上 -j1 -N0 这样晦涩难懂的参数,从而关闭并行处理呢? - Ross Presser
1
@RossPresser 这是一个非常合理的问题。原因是使用GNU Parallel语法可能更容易表达您想要做的事情。想一想:如果没有参数,您将如何连续运行1000次some_command?并且:您能否比parallel -j1 -N0 some_command ::: {1..1000}更简洁地表达它? - Ole Tange
1
如果我的“非常合理的问题”表达得不太合理,我向您道歉。 - Ross Presser
2
在这种特定情况下可能不是那么明显。如果你使用GNU Parallel的更多选项,比如想要重试失败的命令4次并将输出保存到不同的文件中,它会变得更加明显:parallel -N0 -j1 --retries 4 --results outputdir/ some_command - Ole Tange
好的,你已经让这个论点很有说服力了。我不会说我已经成为一个并行转换者,但我可能会在这个周末过目一下。 - Ross Presser
显示剩余2条评论

23

在bash配置文件中(通常是~/.bashrc),一个简单的函数可以很好地工作。

function runx() {
  for ((n=0;n<$1;n++))
    do ${*:2}
  done
}

这样调用它。

$ runx 3 echo 'Hello world'
Hello world
Hello world
Hello world

1
喜欢它。如果它能用ctrl+c取消,那就太好了。 - slashdottir
为什么使用这种方式来回显$RANDOM n次会显示相同的数字? - BladeMight
4
这个工作完美。我需要更改的唯一一件事是,不仅执行do ${*:2},我还添加了eval do eval ${*:2},以确保它适用于不以可执行文件开头的命令。例如,如果您想在运行诸如SOME_ENV=test echo 'test'这样的命令之前设置环境变量。 - 5_nd_5
以下是可以运行 N 次或直到首个非零退出码的版本。它可以通过 ctrl+c 停止。function runx() { success=0 for ((n=0;n<$1 && success==0;n++)) do ${*:2} success=$? done } - Csene

16
for _ in {1..10}; do command; done   

注意使用下划线而不是变量。


11
尽管从技术上讲, "_" 是一个变量名。 - gniourf_gniourf
为什么不直接使用 i 而不是 _,因为 _ 可能会被解释为上一个命令的结果。你也可以通过 for i in {1..10}; do echo "Run $i"; done 来获取迭代次数。 - Joshua Pinter
“_”通常被用作变量名,当变量本身在代码中后面不再使用时,就像这个例子。 - Neurotransmitter

15
如果您希望定期执行此操作,可以运行以下命令每1秒无限期地运行它。您可以放置其他自定义检查来运行n次。
{watch -n 1 some_command}
如果您希望对更改进行视觉确认,请在ls命令之前添加--differences
根据OSX手册页,还有:
{

The --cumulative option makes highlighting "sticky", presenting a running display of all positions that have ever changed. The -t or --no-title option turns off the header showing the interval, command, and current time at the top of the display, as well as the following blank line.

}
Linux/Unix手册页可以在此处找到。

14

你举的例子还有另一种形式:

n=0; while (( n++ < 10 )); do some_command; done

12

xargs速度很

#!/usr/bin/bash
echo "while loop:"
n=0; time while (( n++ < 10000 )); do /usr/bin/true ; done

echo -e "\nfor loop:"
time for ((n=0;n<10000;n++)); do /usr/bin/true ; done

echo -e "\nseq,xargs:"
time seq 10000 | xargs -I{} -P1 -n1 /usr/bin/true

echo -e "\nyes,xargs:"
time yes x | head -n10000 |  xargs -I{} -P1 -n1 /usr/bin/true

echo -e "\nparallel:"
time parallel --will-cite -j1 -N0 /usr/bin/true ::: {1..10000}

在现代的64位Linux上,会输出:

while loop:

real    0m2.282s
user    0m0.177s
sys     0m0.413s

for loop:

real    0m2.559s
user    0m0.393s
sys     0m0.500s

seq,xargs:

real    0m1.728s
user    0m0.013s
sys     0m0.217s

yes,xargs:

real    0m1.723s
user    0m0.013s
sys     0m0.223s

parallel:

real    0m26.271s
user    0m4.943s
sys     0m3.533s

这是有道理的,因为xargs命令是一个单独的本地进程,它会多次生成/usr/bin/true命令,而不是在Bash中解释所有的forwhile循环。当然,这仅适用于单个命令;如果您需要在每次迭代循环中执行多个命令,则与将sh -c 'command1; command2; ...'传递给xargs相比,速度可能会更快。

-P1也可以改为-P8,并行生成8个进程以获得另一个大的速度提升。

我不知道为什么GNU parallel如此缓慢。我认为它应该与xargs相当。


1
GNU Parallel 进行了相当多的设置和簿记工作:它支持可编程替换字符串,缓冲输出,因此两个并行作业的输出永远不会混合,它支持方向(您不能执行:xargs“echo >foo”),并且由于它不是用 bash 编写的,所以必须生成一个新 shell 来进行重定向。然而,主要原因在于 Perl 模块 IPC::open3 - 因此任何关于加快该模块速度的工作都将导致更快的 GNU Parallel。 - Ole Tange

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