首页 文章

如何迭代Bash中变量定义的一系列数字?

提问于
浏览
1144

当变量给出范围时,如何在Bash中迭代一系列数字?

我知道我可以这样做(在Bash documentation中称为"sequence expression"):

for i in {1..5}; do echo $i; done

这使:

1 2 3 4 5

但是,如何用变量替换任何一个范围 endpoints ?这不起作用:

END=5
for i in {1..$END}; do echo $i; done

哪个印刷品:

{1..5}

17 回答

  • 166

    如果您使用的是BSD / OS X,则可以使用jot而不是seq:

    for i in $(jot $END); do echo $i; done
    
  • 52

    您可以使用

    for i in $(seq $END); do echo $i; done
    
  • 339
    for i in $(seq 1 $END); do echo $i; done
    

    编辑:我更喜欢 seq 而不是其他方法,因为我实际上可以记住它;)

  • 88

    讨论

    正如贾加所建议的,使用 seq 是好的 . Pax Diablo建议使用Bash循环以避免调用子进程,如果$ END太大,还有额外的优点:更友好 . Zathrus在循环实现中发现了一个典型的错误,并且还暗示由于 i 是一个文本变量,因此使用相关的减速执行连续转换的往返数字 .

    整数运算

    这是Bash循环的改进版本:

    typeset -i i END
    let END=5 i=1
    while ((i<=END)); do
        echo $i
        …
        let i++
    done
    

    如果我们想要的唯一的东西是 echo ,那么我们可以写 echo $((i++)) .

    ephemient教会了我一些东西:Bash允许 for ((expr;expr;expr)) 构造 . 由于我已经完成了Korn shell( ksh )手册页,那是很久以前的事了),我错过了 .

    所以,

    typeset -i i END # Let's be explicit
    for ((i=1;i<=END;++i)); do echo $i; done
    

    似乎是最节省内存的方式(没有必要分配内存来消耗 seq 的输出,如果END非常大,这可能是一个问题),虽然可能不是“最快的” .

    最初的问题

    eschercycle注意到 Bash表示法只适用于文字;是的,相应于Bash手册 . 没有 exec() 的单个(内部) fork() 可以克服这个障碍(调用 seq 就是这种情况,这是另一个图像需要fork exec):

    for i in $(eval echo "{1..$END}"); do
    

    evalecho 都是Bash内置函数,但命令替换( $(…) 结构)需要 fork() .

  • 14

    另一层间接:

    for i in $(eval echo {1..$END}); do
        ∶
    
  • 5

    The POSIX way

    如果您关心可移植性,请使用example from the POSIX standard

    i=2
    end=5
    while [ $i -le $end ]; do
        echo $i
        i=$(($i+1))
    done
    

    输出:

    2
    3
    4
    5
    

    不是POSIX的东西:

    如果shell变量x包含一个形成有效整数常量的值,可选地包括前导加号或减号,那么算术扩展“$((x))”和“$(($ x))”将返回相同的 Value .

    但从字面上读它并不意味着自 x+1 以来 $((x+1)) 扩展并不是一个变量 .

  • 4

    如果您希望尽可能接近大括号表达式语法,请尝试range function from bash-tricks' range.bash .

    例如,以下所有内容与 echo {1..10} 完全相同:

    source range.bash
    one=1
    ten=10
    
    range {$one..$ten}
    range $one $ten
    range {1..$ten}
    range {1..10}
    

    它尝试使用尽可能少的"gotchas"来支持本机bash语法:不仅支持变量,而且还防止了作为字符串(例如 for i in {1..a}; do echo $i; done )提供的无效范围的常常不合需要的行为 .

    其他答案在大多数情况下都有效,但它们都至少有以下缺点之一:

    • 其中许多人使用subshells,在某些系统上可以使用harm performancemay not be possible .

    • 他们中的许多人依赖外部程序 . 甚至 seq 是必须安装才能使用的二进制文件,必须由bash加载,并且必须包含您期望的程序,以便在这种情况下工作 . 无论是否普遍存在,除了Bash语言本身之外,还有更多的东西需要依赖 .

    • 仅使用本机Bash功能的解决方案,如@ ephemient,将不适用于字母范围,如 {a..z} ;支撑扩张将 . 问题是关于数字的范围,所以这是一个狡辩 .

    • 它们中的大多数在视觉上与 {1..10} 大括号扩展范围语法不相似,因此使用两者的程序可能更难以阅读 .

    • @ bobbogo的答案使用了一些熟悉的语法,但如果 $END 变量不是该范围另一侧的有效范围"bookend",则会出现意外情况 . 例如,如果 END=a ,则不会发生错误,并且将回显逐字值 {1..a} . 这也是Bash的默认行为 - 它通常是意料之外的 .

    免责声明:我是链接代码的作者 .

  • 1273

    {} 替换为 (( ))

    tmpstart=0;
    tmpend=4;
    
    for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
    echo $i ;
    done
    

    产量:

    0
    1
    2
    3
    4
    
  • 6

    seq 方法是最简单的,但Bash具有内置的算术评估 .

    END=5
    for ((i=1;i<=END;i++)); do
        echo $i
    done
    # ==> outputs 1 2 3 4 5 on separate lines
    

    for ((expr1;expr2;expr3)); 构造与C中的 for (expr1;expr2;expr3) 类似语言,和其他 ((expr)) 情况一样,Bash将它们视为算术 .

  • 12

    如果你正在做shell命令而且你(像我一样)对流水线有一种迷恋,那么这个很好:

    seq 1 $END | xargs -I {} echo {}

  • 0

    这些都很好,但seq应该被弃用,大多数只适用于数值范围 .

    如果用双引号括起for循环,则在回显字符串时将取消引用开始和结束变量,并且可以将字符串右回送到BASH执行 . $i 需要使用\来进行转义,因此在发送到子shell之前不会对其进行评估 .

    RANGE_START=a
    RANGE_END=z
    echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash
    

    此输出也可以分配给变量:

    VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash`
    

    这应该生成的唯一“开销”应该是bash的第二个实例,因此它应该适用于密集操作 .

  • 29

    这适用于Bash和Korn,也可以从较高的数字到较低的数字 . 可能不是最快或最漂亮,但运作良好 . 处理否定也是如此 .

    function num_range {
       # Return a range of whole numbers from beginning value to ending value.
       # >>> num_range start end
       # start: Whole number to start with.
       # end: Whole number to end with.
       typeset s e v
       s=${1}
       e=${2}
       if (( ${e} >= ${s} )); then
          v=${s}
          while (( ${v} <= ${e} )); do
             echo ${v}
             ((v=v+1))
          done
       elif (( ${e} < ${s} )); then
          v=${s}
          while (( ${v} >= ${e} )); do
             echo ${v}
             ((v=v-1))
          done
       fi
    }
    
    function test_num_range {
       num_range 1 3 | egrep "1|2|3" | assert_lc 3
       num_range 1 3 | head -1 | assert_eq 1
       num_range -1 1 | head -1 | assert_eq "-1"
       num_range 3 1 | egrep "1|2|3" | assert_lc 3
       num_range 3 1 | head -1 | assert_eq 3
       num_range 1 -1 | tail -1 | assert_eq "-1"
    }
    
  • 5

    这在 bash 中工作正常:

    END=5
    i=1 ; while [[ $i -le $END ]] ; do
        echo $i
        ((i = i + 1))
    done
    
  • 5

    这是另一种方式:

    end=5
    for i in $(bash -c "echo {1..${end}}"); do echo $i; done
    
  • 7

    这就是原始表达不起作用的原因 .

    来自man bash:

    在任何其他扩展之前执行括号扩展,并且在结果中保留对其他扩展特殊的任何字符 . 这是严格的文字 . Bash不对扩展的上下文或大括号之间的文本应用任何语法解释 .

    因此,在参数扩展之前,大括号扩展是一种纯粹的文本宏操作 .

    Shell是宏处理器和更正式的编程语言之间高度优化的混合 . 为了优化典型的用例,语言变得更加复杂,并且接受了一些限制 .

    推荐

    我建议坚持使用Posix1功能 . 这意味着使用 for i in <list>; do ,如果列表已知,否则,请使用 whileseq ,如下所示:

    #!/bin/sh
    
    limit=4
    
    i=1; while [ $i -le $limit ]; do
      echo $i
      i=$(($i + 1))
    done
    # Or -----------------------
    for i in $(seq 1 $limit); do
      echo $i
    done
    

    1. Bash是一个很棒的shell,我以交互方式使用它,但我没有将bash-isms放入我的脚本中 . 脚本可能需要更快的shell,更安全的shell,更嵌入式的shell . 他们可能需要运行在/ bin / sh上安装的任何东西,然后有所有通常的专业标准参数 . 记得shellshock,又名bashdoor?
  • 18

    如果你需要它的前缀比你想要的更多

    for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done
    

    会产生的

    07
    08
    09
    10
    11
    12
    
  • 21

    我知道这个问题是关于 bash ,但是 - 只是为了记录 - ksh93 更聪明并按预期实现它:

    $ ksh -c 'i=5; for x in {1..$i}; do echo "$x"; done'
    1
    2
    3
    4
    5
    $ ksh -c 'echo $KSH_VERSION'
    Version JM 93u+ 2012-02-29
    
    $ bash -c 'i=5; for x in {1..$i}; do echo "$x"; done'
    {1..5}
    

相关问题