当范围由变量给定时,如何在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}
当前回答
如果希望尽可能接近大括号表达式语法,请尝试使用bash-tricks的range.bash中的range函数。
例如,以下所有操作将执行与echo{1..10}完全相同的操作:
source range.bash
one=1
ten=10
range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}
它试图用尽可能少的“gotchas”来支持本机bash语法:不仅支持变量,而且还防止了作为字符串提供的无效范围(例如,对于{1..a}中的i;do echo$i;done)的通常不希望的行为。
其他答案在大多数情况下都有效,但它们都至少有以下缺点之一:
其中许多都使用子外壳,这可能会损害性能,在某些系统上可能不可能。他们中的许多人依赖外部程序。甚至seq也是一个二进制文件,必须安装才能使用,必须由bash加载,并且必须包含您期望的程序,才能在这种情况下工作。无论是否无处不在,这比Bash语言本身更值得依赖。只使用本机Bash功能的解决方案,如@ephemient,将无法在字母范围内工作,如{a..z};支架膨胀将。不过,问题是关于数字的范围,所以这是一个狡辩。它们中的大多数在视觉上与{1..10}大括号扩展范围语法并不相似,因此使用这两种语法的程序可能有点难以阅读。@bobobogo的答案使用了一些熟悉的语法,但如果$END变量不是范围另一侧的有效范围“bookend”,则会出现一些意外情况。例如,如果END=a,则不会发生错误,并且将回显逐字值{1..a}。这也是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方式
如果您关心可移植性,请使用POSIX标准中的示例:
i=2
end=5
while [ $i -le $end ]; do
echo $i
i=$(($i+1))
done
输出:
2
3
4
5
非POSIX的事物:
(())没有美元,尽管它是POSIX本身提到的常见扩展。[[.]在这里就足够了。另请参见:Bash中的单方括号和双方括号之间的区别是什么?用于((;;))seq(GNU内核){start..end},并且无法使用Bash手册中提到的变量。设i=i+1:POSIX 7 2。Shell命令语言不包含单词let,并且在bash上失败--posix 4.3.42i=$i+1的美元可能是必需的,但我不确定。POSIX 7 2.6.4算术扩展表示:如果外壳变量x包含形成有效整数常量的值,可选地包括前导加号或减号,则算术展开式“$((x))”和“$($x)”应返回相同的值。但从字面上看,这并不意味着$((x+1))会膨胀,因为x+1不是一个变量。
如果你需要前缀,你可能会喜欢这个
for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done
这将产生
07
08
09
10
11
12
我结合了这里的一些想法并衡量了性能。
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
如果希望尽可能接近大括号表达式语法,请尝试使用bash-tricks的range.bash中的range函数。
例如,以下所有操作将执行与echo{1..10}完全相同的操作:
source range.bash
one=1
ten=10
range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}
它试图用尽可能少的“gotchas”来支持本机bash语法:不仅支持变量,而且还防止了作为字符串提供的无效范围(例如,对于{1..a}中的i;do echo$i;done)的通常不希望的行为。
其他答案在大多数情况下都有效,但它们都至少有以下缺点之一:
其中许多都使用子外壳,这可能会损害性能,在某些系统上可能不可能。他们中的许多人依赖外部程序。甚至seq也是一个二进制文件,必须安装才能使用,必须由bash加载,并且必须包含您期望的程序,才能在这种情况下工作。无论是否无处不在,这比Bash语言本身更值得依赖。只使用本机Bash功能的解决方案,如@ephemient,将无法在字母范围内工作,如{a..z};支架膨胀将。不过,问题是关于数字的范围,所以这是一个狡辩。它们中的大多数在视觉上与{1..10}大括号扩展范围语法并不相似,因此使用这两种语法的程序可能有点难以阅读。@bobobogo的答案使用了一些熟悉的语法,但如果$END变量不是范围另一侧的有效范围“bookend”,则会出现一些意外情况。例如,如果END=a,则不会发生错误,并且将回显逐字值{1..a}。这也是Bash的默认行为——这通常是意外的。
免责声明:我是链接代码的作者。