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

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

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

这给出了:

1.2.3.4.5.

然而,如何用变量替换任意一个范围端点?这不起作用:

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

哪些打印:

{1..5}


当前回答

这是另一种方式:

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

其他回答

将{}替换为(()):

tmpstart=0;
tmpend=4;

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

产量:

0
1
2
3
4

我结合了这里的一些想法并衡量了性能。

TL;DR要点:

seq和{..}真的很快for和while循环很慢$()速度慢for((;;))循环较慢$(())甚至更慢担心内存中的N个数字(seq或{..})是愚蠢的(至少高达100万)

这些都不是结论。您必须查看这些代码背后的C代码才能得出结论。这更多的是关于我们如何使用这些机制来循环代码。大多数单次操作的速度都接近于相同的速度,在大多数情况下这并不重要。但是,像for((i=1;i<=1000000;i++))这样的机制有很多操作,正如您可以看到的那样。每个循环的操作数也比从i中得到的要多得多,单位为$(seq110000)。这对你来说可能并不明显,这就是为什么做这样的测试是有价值的。

演示

# 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
for i in $(seq 1 $END); do echo $i; done

编辑:比起其他方法,我更喜欢seq,因为我能记住它;)

讨论

正如加亚罗所建议的那样,使用seq是可以的。Pax Diablo建议使用Bash循环来避免调用子进程,如果$END太大,则具有更友好内存的额外优势。Zathur发现了循环实现中的一个典型错误,并暗示,由于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允许((expr;expr;expr))构造。由于我从未阅读过Bash的整个手册页(就像我很久以前在Kornshell(ksh)手册页上所做的那样),所以我错过了这一点。

So,

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

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

最初的问题

eschercycle注意到{a..b}Bash表示法仅适用于文字;根据Bash手册,这是正确的。可以用一个单独的(内部)fork()而不使用exec()来克服这个障碍(就像调用seq一样,这是另一个映像,需要fork+exec):

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

eval和echo都是Bash内置的,但是命令替换($(…)构造)需要一个fork()。

如果您不想使用“seq”或“eval”或jot或算术扩展格式,例如for((i=1;i<=END;i++)),或其他循环,例如while,并且您不想只使用“printf”和“echo”,那么这个简单的解决方法可能适合您的预算:

a=1;b=5;d=‘对于{‘$a‘..‘$b‘}中的i;do echo-n“$i”;完成;'echo“$d”|bash

PS:反正我的bash没有“seq”命令。

在Mac OSX 10.6.8、Bash 3.2.48上测试