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

如何迭代Bash中由变量定义的一系列数字?,bash,shell,for-loop,syntax,Bash,Shell,For Loop,Syntax,当范围由变量给定时,如何在Bash中迭代一个数字范围 我知道我可以做到这一点(在Bash中称为“序列表达式”): 其中: 1 2 3 4 五, 但是,如何用变量替换范围端点?这不起作用: END=5 for i in {1..$END}; do echo $i; done 其中打印: {1..5} 你可以用 for i in $(seq $END); do echo $i; done 编辑:与其他方法相比,我更喜欢seq,因为我实际上可以记住它;) 这在bash中运行良好: END=5 i=

当范围由变量给定时,如何在Bash中迭代一个数字范围

我知道我可以做到这一点(在Bash中称为“序列表达式”):

其中:

1
2
3
4
五,

但是,如何用变量替换范围端点?这不起作用:

END=5
for i in {1..$END}; do echo $i; done
其中打印:

{1..5}

你可以用

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

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

这在
bash
中运行良好:

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done
讨论 正如Jiaaro所建议的,使用
seq
是可以的。Pax Diablo建议使用Bash循环来避免调用子进程,如果$END太大,它还有一个额外的优点,即对内存更友好。Zathrus在循环实现中发现了一个典型的bug,并且还暗示由于
i
是一个文本变量,因此在执行与数字之间的连续转换时会相应地降低速度

整数运算 这是Bash循环的改进版本:

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    …
    let i++
done
似乎是最节省内存的方法(不需要分配内存来消耗
seq
的输出,如果END非常大,这可能会成为一个问题),尽管可能不是“最快的”

第一个问题 eschercycle指出,{a..b}Bash表示法仅适用于文本;根据Bash手册,为true。使用单个(内部)
fork()
而不使用
exec()
(与调用
seq
的情况相同,另一个映像需要fork+exec):


eval
echo
都是Bash内置的,但是命令替换(
$(…)
构造)需要
fork()
。seq方法是最简单的,但是Bash有内置的算术求值

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines
END=5
对于((i=1;i在单独的行上输出1 2 3 4 5

((expr1;expr2;expr3));构造的工作原理与C和类似语言中的((expr1;expr2;expr3)类似,与其他
((expr))
情况类似,Bash将它们视为算术。

另一层间接寻址:

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

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

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

以下是原始表达式不起作用的原因

来自man bash:

支架展开在安装之前执行 任何其他扩展,以及 其他特殊字符 扩展保存在 结果。它是严格的文本。Bash 不应用任何语法 对语境的解读 扩展或 支架

因此,大括号扩展是在参数扩展之前,作为纯文本宏操作进行的

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

推荐

我建议坚持使用Posix1功能。这意味着如果列表已知,则使用
for I in;do
,否则,使用
while
seq
,如下所示:

#!/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吗?

这些是ll很好,但是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的第二个实例,因此它应该适合密集型操作。

我知道这个问题是关于
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}

{}
替换为
(())


POSIX方式

如果您关心便携性,请使用:

输出:

2
3
4
5
不是POSIX的东西:

  • (())
    不带美元,尽管这是一种常见的扩展
  • [[
    [
    在这里就足够了。另请参见:
  • (;)
    for(;)
  • seq
    (GNU Coreutils)
  • {start..end}
    ,并且不能使用前面提到的变量
  • let i=i+1
    :不包含单词
    let
    ,并且在
    bash--posix上失败
  • 可能需要
    i=$i+1
    的美元,但我不确定。表示:

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

    但是逐字阅读并不意味着
    $((x+1))
    会扩展,因为
    x+1
    不是一个变量

    • 这是另一种方式:

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

      如果您正在执行shell命令,并且您(像我一样)对管道有一种癖好,那么这一个很好:


      seq 1$END | xargs-I{}echo{}
      如果您需要它的前缀,您可能会喜欢它

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

      如果希望尽可能接近大括号表达式语法,请尝试

      例如,以下所有操作都将执行与
      echo{1..10}
      完全相同的操作:

      source range.bash
      one=1
      ten=10
      
      range {$one..$ten}
      range $one $ten
      range {1..$ten}
      range {1..10}
      
      它试图用尽可能少的“gotchas”来支持本机bash语法:不仅支持变量,而且支持i
      tmpstart=0;
      tmpend=4;
      
      for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
      echo $i ;
      done
      
      0
      1
      2
      3
      4
      
      i=2
      end=5
      while [ $i -le $end ]; do
          echo $i
          i=$(($i+1))
      done
      
      2
      3
      4
      5
      
      end=5
      for i in $(bash -c "echo {1..${end}}"); do echo $i; done
      
       for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done
      
      07
      08
      09
      10
      11
      12
      
      source range.bash
      one=1
      ten=10
      
      range {$one..$ten}
      range $one $ten
      range {1..$ten}
      range {1..10}
      
      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"
      }
      
      # show that seq is fast
      $ time (seq 1 1000000 | wc)
       1000000 1000000 6888894
      
      real    0m0.227s
      user    0m0.239s
      sys     0m0.008s
      
      # show that {..} is fast
      $ time (echo {1..1000000} | wc)
             1 1000000 6888896
      
      real    0m1.778s
      user    0m1.735s
      sys     0m0.072s
      
      # Show that for loops (even with a : noop) are slow
      $ time (for i in {1..1000000} ; do :; done | wc)
             0       0       0
      
      real    0m3.642s
      user    0m3.582s
      sys 0m0.057s
      
      # show that echo is slow
      $ time (for i in {1..1000000} ; do echo $i; done | wc)
       1000000 1000000 6888896
      
      real    0m7.480s
      user    0m6.803s
      sys     0m2.580s
      
      $ time (for i in $(seq 1 1000000) ; do echo $i; done | wc)
       1000000 1000000 6888894
      
      real    0m7.029s
      user    0m6.335s
      sys     0m2.666s
      
      # show that C-style for loops are slower
      $ time (for (( i=1; i<=1000000; i++ )) ; do echo $i; done | wc)
       1000000 1000000 6888896
      
      real    0m12.391s
      user    0m11.069s
      sys     0m3.437s
      
      # show that arithmetic expansion is even slower
      $ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; i=$(($i+1)); done | wc)
       1000000 1000000 6888896
      
      real    0m19.696s
      user    0m18.017s
      sys     0m3.806s
      
      $ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; ((i=i+1)); done | wc)
       1000000 1000000 6888896
      
      real    0m18.629s
      user    0m16.843s
      sys     0m3.936s
      
      $ time (i=1; e=1000000; while [ $i -le $e ]; do echo $((i++)); done | wc)
       1000000 1000000 6888896
      
      real    0m17.012s
      user    0m15.319s
      sys     0m3.906s
      
      # even a noop is slow
      $ time (i=1; e=1000000; while [ $((i++)) -le $e ]; do :; done | wc)
             0       0       0
      
      real    0m12.679s
      user    0m11.658s
      sys 0m1.004s
      
      $ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last
      
      $ seq 1 2 10
      1 3 5 7 9
      
      $ seq 1 5
      1 2 3 4 5
      
      $ seq 5
      1 2 3 4 5
      
      $ echo {1..5}
      1 2 3 4 5
      
      $ echo {1..10..2}
      1 3 5 7 9
      
      $ echo {a..z}
      a b c d e f g h i j k l m n o p q r s t u v w x y z