【问题标题】:Is there a better way to run a command N times in bash?有没有更好的方法在 bash 中运行命令 N 次?
【发布时间】:2011-04-13 20:34:42
【问题描述】:

我偶尔会像这样运行 bash 命令行:

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

连续运行some_command 多次 -- 在本例中为 10 次。

some_command 通常是一个命令链或管道。

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

【问题讨论】:

  • 除了其他不错的答案外,您还可以使用let ++n 代替n=$((n+1))(少3 个字符)。
  • 这些方法中哪些是可移植的一些迹象会很好。
  • 如果你愿意换壳,zshrepeat 10 do some_command; done
  • @JohnVandivier 我刚刚在新的 18.04 docker 容器中的 bash 中尝试过,我的问题中发布的原始语法仍然有效。也许您正在运行 bash 以外的 shell,例如 /bin/sh,这确实是 dash,在这种情况下,我会返回 sh: 1: [[: not found

标签: bash loops


【解决方案1】:

使用常量:

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

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

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

【讨论】:

  • 这是最接近编程语言的方法^^ - 应该是公认的!
  • 这个更好,因为它是单行,因此更容易在终端中使用
  • 您也可以使用变量,例如x=10for ((n=0; n &lt; $x; n++));
  • @Kunok 在一行中运行已接受的答案同样可以接受:for run in {1..10}; do echo "on run $run"; done
  • 如果n 已设置为特定值怎么办? for (( ; n&lt;10; n++ )) 不起作用/编辑:最好使用其他答案之一,例如 while (( n++... 一个
【解决方案2】:

如果你的范围有一个变量,use 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

【讨论】:

  • 如果你有很多迭代,for (n=0;n&lt;k;n++)) 的表单可能会更好;我怀疑{1..k} 将实现一个字符串,所有这些整数都用空格分隔。
  • @Joe Koberg,感谢您的提示。我通常使用 N
  • @bstpierre:大括号展开形式不能(轻松地)使用变量来指定 Bash 中的范围。
  • 确实如此。大括号扩展是在变量扩展之前根据gnu.org/software/bash/manual/bashref.html#Brace-Expansion进行的,因此它永远不会看到任何变量的值。
  • 变量扩展的可悲之处。我正在使用以下循环 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
【解决方案3】:

(bashref)Looping Constructs 中提到的for 的替代形式怎么样?

【讨论】:

  • 给他一块骨头,并举一个替代形式的例子?
【解决方案4】:

首先,您可以将其包装在一个函数中:

function manytimes {
    n=0
    times=$1
    shift
    while [[ $n -lt $times ]]; do
        $@
        n=$((n+1))
    done
}

这样称呼:

$ manytimes 3 echo "test" | tr 'e' 'E'
tEst
tEst
tEst

【讨论】:

  • 如果要运行的命令比没有重定向的简单命令更复杂,这将不起作用。
  • 您可以使其适用于更复杂的情况,但您可能必须将命令包装在函数或脚本中。
  • 这里的tr 具有误导性,它正在处理manytimes 的输出,而不是echo。我认为您应该将其从示例中删除。
【解决方案5】:

你的例子的另一种形式:

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

【讨论】:

    【解决方案6】:

    xargs 和 seq 会有所帮助

    function __run_times { seq 1 $1| { shift; xargs -i -- "$@"; } }
    

    观点:

    abon@abon:~$ __run_times 3  echo hello world
    hello world
    hello world
    hello world
    

    【讨论】:

    • 命令中的 shift 和双破折号有什么作用? “转变”真的有必要吗?
    • 如果命令比没有重定向的简单命令更复杂,这将不起作用。
    • 非常慢,回显 $RANDOM 50 次需要 7 秒,即便如此它每次运行显示相同的随机数...
    【解决方案7】:

    For 循环可能是正确的方法,但这里有一个有趣的替代方法:

    echo -e {1..10}"\n" |xargs -n1 some_command

    如果您需要迭代次数作为调用的参数,请使用:

    echo -e {1..10}"\n" |xargs -I@ echo now I am running iteration @

    编辑: 正确地评论说,上面给出的解决方案只有通过简单的命令运行(没有管道等)才能顺利运行。你总是可以使用sh -c 来做更复杂的事情,但不值得。

    我通常使用的另一种方法是以下函数:

    rep() { s=$1;shift;e=$1;shift; for x in `seq $s $e`; do c=${@//@/$x};sh -c "$c"; done;}

    现在你可以这样称呼它:

    rep 3 10 echo iteration @

    前两个数字给出了范围。 @ 将被转换为迭代号。现在您也可以将其与管道一起使用:

    rep 1 10 "ls R@/|wc -l"

    为您提供目录 R1 .. R10 中的文件数。

    【讨论】:

    • 如果命令比没有重定向的简单命令更复杂,这将不起作用。
    • 很公平,但请参阅上面的编辑。编辑后的方法使用 sh -c,因此也应该使用重定向。
    • rep 1 2 touch "foo @" 不会创建两个文件“foo 1”和“foo 2”;而是创建三个文件“foo”、“1”和“2”。可能有一种方法可以使用printf%q 来正确引用,但是将任意单词序列正确引用到单个字符串中以传递给sh -c 会很棘手。
    • OP 要求更多简洁,那么既然已经有 5 个更简洁的答案,为什么还要添加这样的内容?
    • .bashrc 中定义rep 的定义后,进一步的调用会变得更加简洁。
    【解决方案8】:

    另一种简单的破解方法:

    seq 20 | xargs -Iz echo "Hi there"
    

    运行 echo 20 次。


    注意seq 20 | xargs -Iz echo "Hi there z" 会输出:

    你好 1
    你好 2
    ...

    【讨论】:

    • 你甚至不需要 1(可以运行 seq 20
    • 2015 版:seq 20 | xargs -I{} echo "Hi there {}"
    • 请注意,z 不是一个好的占位符,因为它很常见,可以作为命令文本中的常规文本。使用{}会更好。
    • 有什么方法可以丢弃seq 的号码?所以它只会打印Hi there 20 次(没有数字)?编辑:只需使用-I{},然后在命令中没有任何{}
    • 使用一些东西,你确定它不会出现在输出中,例如IIIII,那么它看起来不错例如:seq 20 | xargs -IIIIII timeout 10 yourCommandThatHangs
    【解决方案9】:

    使用 GNU Parallel 你可以做到:

    parallel some_command ::: {1..1000}
    

    如果您不想将数字作为参数并且一次只运行一个作业:

    parallel -j1 -N0 some_command ::: {1..1000}
    

    观看介绍视频以快速了解: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

    浏览教程 (http://www.gnu.org/software/parallel/parallel_tutorial.html)。你命令行 爱你。

    【讨论】:

    • 你为什么要使用一个工具,其存在的根本原因,正如它的名字所证明的那样,是并行运行,然后给它神秘的参数-j1 -N0 关闭并行性????
    • @RossPresser 这是一个非常合理的问题。原因是使用 GNU Parallel 语法可能更容易表达您想要做的事情。想一想:你如何在没有参数的情况下连续运行some_command 1000 次?还有:你能比parallel -j1 -N0 some_command ::: {1..1000}更短吗?
    • 嗯,我想你有点意思,但它仍然感觉就像使用一个精心制作的发动机组作为锤子。如果我有足够的动力,并且对我未来的继任者试图理解我所做的事情感到体谅,我可能会将困惑包装在一个 shell 脚本中,然后用repeat.sh repeatcount some_command 调用它。为了在 shell 脚本中实现,我可能会使用parallel,如果它已经安装在机器上(可能不会)。或者我可能会倾向于 xargs,因为当some_command 不需要重定向时,这似乎是最快的。
    • 如果我的“非常合理的问题”以不合理的方式表达,我深表歉意。
    • 在这种特殊情况下,它可能不是那么明显。如果您使用 GNU Parallel 的更多选项,它会变得更加明显,例如,如果您想重试失败的命令 4 次并将输出保存到不同的文件:parallel -N0 -j1 --retries 4 --results outputdir/ some_command
    【解决方案10】:
    for _ in {1..10}; do command; done   
    

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

    【讨论】:

    • 虽然从技术上讲,_ 是一个变量名。
    • 是的,为什么不直接使用i 而不是_,这可能意味着前面命令的结果。你也可以像for i in {1..10}; do echo "Run $i"; done这样获取迭代号。
    【解决方案11】:

    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 并行这么慢。我原以为它可以与 xargs 相媲美。

    【讨论】:

    • GNU Parallel 做了很多设置和记账:它支持可编程替换字符串,它缓冲输出,因此两个并行作业的输出永远不会混合,它支持方向(你不能这样做:xargs "echo > foo") 并且因为它不是用 bash 编写的,所以它必须生成一个新的 shell 来进行重定向。然而,主要原因在于 Perl 模块 IPC::open3 - 因此任何提高速度的工作都会导致 GNU Parallel 速度更快。
    【解决方案12】:

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

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

    这样称呼它。

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

    【讨论】:

    • 喜欢它。现在如果可以用ctrl+c取消就太好了
    • 为什么用这种方式回显$RANDOM n次显示相同的数字?
    • 这非常有效。我唯一需要改变的不是只做do ${*:2},而是添加了eval do eval ${*:2},以确保它适用于运行不以可执行文件开头的命令。例如,如果您想在运行 SOME_ENV=test echo 'test' 之类的命令之前设置环境变量。
    【解决方案13】:

    如果您使用的是 zsh shell:

    repeat 10 { echo 'Hello' }
    

    其中 10 是命令重复的次数。

    【讨论】:

    • 虽然它不是问题的答案(因为它明确提到了 bash),但它非常有用并帮助我解决了我的问题。 +1
    • 另外,如果你只是在终端输入,你可以使用repeat 10 { !! }
    • 有什么方法可以获取block中的run number吗?例如{ echo "Run number $n" }
    【解决方案14】:

    如果您可以定期执行此操作,则可以运行以下命令以无限期地每 1 秒运行一次。您可以设置其他自定义检查以运行 n 次。

    watch -n 1 some_command

    如果您希望直观地确认更改,请在 ls 命令之前附加 --differences

    根据 OSX 手册页,还有

    --cumulative 选项使突出显示“粘性”,呈现一个 运行显示所有已更改的位置。 -t 或 --no-title 选项关闭显示间隔的标题, 命令,显示屏顶部的当前时间,以及 下面的空行。

    Linux/Unix 手册页可以找到here

    【讨论】:

      【解决方案15】:

      所有现有答案似乎都需要 bash,并且不适用于标准 BSD UNIX /bin/sh(例如,ksh on OpenBSD)。

      以下代码适用于任何 BSD:

      $ echo {1..4}
      {1..4}
      $ seq 4
      sh: seq: not found
      $ for i in $(jot 4); do echo e$i; done
      e1
      e2
      e3
      e4
      $
      

      【讨论】:

        【解决方案16】:

        还有一个答案:对空参数使用参数扩展:

        # calls curl 4 times 
        curl -s -w "\n" -X GET "http:{,,,}//www.google.com"
        

        在 Centos 7 和 MacOS 上测试。

        【讨论】:

        • 这很有趣,您能否提供更多详细信息说明为什么会这样?
        • 它正在运行参数扩展 n 次,但由于参数为空,它实际上并没有改变运行的内容
        • 搜索参数展开和curl都没有结果。我几乎不知道卷曲。如果你能指出一些文章或者这个功能的另一个通用名称,那就太棒了。谢谢。
        • 我认为这可能会有所帮助gnu.org/software/bash/manual/html_node/…
        【解决方案17】:

        脚本文件

        bash-3.2$ cat test.sh 
        #!/bin/bash
        
        echo "The argument is  arg: $1"
        
        for ((n=0;n<$1;n++));
        do
          echo "Hi"
        done
        

        以及下面的输出

        bash-3.2$  ./test.sh 3
        The argument is  arg: 3
        Hi
        Hi
        Hi
        bash-3.2$
        

        【讨论】:

          【解决方案18】:

          我用这个循环解决了,其中 repeat 是一个整数,表示循环的次数

          repeat=10
          for n in $(seq $repeat); 
              do
                  command1
                  command2
              done
          

          【讨论】:

            【解决方案19】:

            有点天真,但这是我通常记得的:

            for i in 1 2 3; do
              some commands
            done
            

            与@joe-koberg 的回答非常相似。他的更好,特别是如果您需要多次重复,只是让我更难记住其他语法,因为在过去几年里我没有经常使用bash。我的意思是至少不是为了编写脚本。

            【讨论】:

              【解决方案20】:

              您可以使用此命令重复您的命令 10 次或更多次

              for i in {1..10}; do **your command**; done
              

              例如

              for i in {1..10}; do **speedtest**; done
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-04-04
                • 1970-01-01
                • 2011-05-02
                • 1970-01-01
                • 2015-05-15
                • 1970-01-01
                相关资源
                最近更新 更多