当范围由变量给定时,如何在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}
当范围由变量给定时,如何在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}
当前回答
我结合了这里的一些想法并衡量了性能。
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,因为我能记住它;)
这适用于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"
}
有很多方法可以做到这一点,但我更喜欢的方法如下
使用seq
man-seq简介
$ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last
语法
完整命令seq第一次递增最后一次
first是序列中的起始编号[可选,默认为:1]incr是增量[是可选的,默认为:1]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
使用{first..last..incr}
这里第一个和最后一个是强制性的,incr是可选的
只使用第一个和最后一个
$ echo {1..5}
1 2 3 4 5
使用incr
$ 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
这是另一种方式:
end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
我知道这个问题是关于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}