当变量给出范围时,如何在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
Jiaaro.. 1578
for i in $(seq 1 $END); do echo $i; done
编辑:我更喜欢seq
其他方法,因为我实际上可以记住它;)
for i in $(seq 1 $END); do echo $i; done
编辑:我更喜欢seq
其他方法,因为我实际上可以记住它;)
该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));
构造就像for (expr1;expr2;expr3)
在C和类似语言中一样工作,并且像其他((expr))
情况一样,Bash将它们视为算术.
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))
构造.因为我从来没有读过Bash的整个手册页(就像我已经完成了Korn shell(ksh
)手册页,那是很久以前的事了),我错过了.
所以,
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()
是所必需的命令取代(所述$(…)
构建体).
这就是原始表达不起作用的原因.
来自man bash:
在任何其他扩展之前执行大括号扩展,并且在结果中保留对其他扩展特殊的任何字符.这是严格的文字.Bash不对扩展的上下文或大括号之间的文本应用任何语法解释.
因此,在参数扩展之前,大括号扩展是一种纯粹的文本宏操作.
Shell是宏处理器和更正式的编程语言之间高度优化的混合.为了优化典型的用例,语言变得更加复杂,并且接受了一些限制.
建议
我建议坚持使用Posix 1功能.这意味着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
POSIX的方式
如果您关心可移植性,请使用POSIX标准中的示例:
i=2 end=5 while [ $i -le $end ]; do echo $i i=$(($i+1)) done
输出:
2 3 4 5
事情是不是 POSIX:
(( ))
没有美元,虽然它是POSIX本身提到的常见扩展.
[[
.[
这就够了 另请参阅:Bash中单方括号和双方括号之间有什么区别?
for ((;;))
seq
(GNU Coreutils)
{start..end}
,这不适用于Bash手册中提到的变量.
let i=i+1
:POSIX 7 2. Shell命令语言不包含该单词let
,并且在bash --posix
4.3.42上失败
i=$i+1
可能需要美元,但我不确定.POSIX 7 2.6.4算术扩展说:
如果shell变量x包含一个形成有效整数常量的值,可选地包括前导加号或减号,那么算术扩展"$((x))"和"$(($ x))"将返回相同的值值.
但从字面上读它并不意味着$((x+1))
扩展,因为x+1
它不是一个变量.
另一层间接:
for i in $(eval echo {1..$END}); do ?
您可以使用
for i in $(seq $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
如果您使用的是BSD/OS X,则可以使用jot而不是seq:
for i in $(jot $END); do echo $i; done
这适用于bash
:
END=5 i=1 ; while [[ $i -le $END ]] ; do echo $i ((i = i + 1)) done
我在这里结合了一些想法并评估了性能。
TL; DR外卖:seq
而且{..}
真的很快
for
和while
循环很慢
$( )
是缓慢的
for (( ; ; ))
循环较慢
$(( ))
甚至更慢
担心内存中的N个数字(seq或{..})很愚蠢(至少一百万)。
这些不是结论。您必须查看每一个背后的C代码才能得出结论。这更多地是关于我们如何倾向于使用这些机制中的每一个来循环代码。大多数单次操作的速度足以接近大多数情况下无关紧要的速度。但是for (( i=1; i<=1000000; i++ ))
,您可以从视觉上看到类似的机制,其中包括许多操作。这也是更多的操作每圈比你得到for i in $(seq 1 1000000)
。这对您来说可能并不明显,这就是进行这样的测试很有价值的原因。
# 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
,但是 - 只是为了记录 - 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}
这是另一种方式:
end=5 for i in $(bash -c "echo {1..${end}}"); do echo $i; done
如果您想尽可能地与brace-expression语法保持一致,请尝试range
从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}
它试图以尽可能少的“陷阱”来支持本机bash语法:不仅支持变量,而且for i in {1..a}; do echo $i; done
还防止了无效范围通常以字符串(例如)形式提供的不良行为。
其他答案在大多数情况下都可以使用,但是它们都至少具有以下缺点之一:
它们中的许多使用子外壳,这会损害性能,并且在某些系统上可能无法实现。
其中许多依赖于外部程序。Even seq
是必须安装才能使用的二进制文件,必须由bash加载,并且必须包含您期望的程序,这样它才能在这种情况下起作用。不论是否泛滥,它所依赖的不仅仅是Bash语言本身。
仅使用本地Bash功能(例如@ephemient的功能)的解决方案将不适用于字母范围,例如{a..z}
; 大括号扩展会。但是,问题是关于数字的范围,所以这是一个小问题。
它们中的大多数在外观上都不与{1..10}
大括号扩展范围语法相似,因此使用这两种语法的程序可能会更难阅读。
@bobbogo的答案使用了一些熟悉的语法,但是如果$END
变量不是该范围另一侧的有效范围“ bookend”,则会发生意外情况。END=a
例如,如果使用,则不会发生错误,并且{1..a}
将回显逐字记录值。这也是Bash的默认行为-经常是出乎意料的。
免责声明:我是链接代码的作者。
这些都很好,但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的第二个实例,因此它应该适用于密集操作.
替换{}
为(( ))
:
tmpstart=0; tmpend=4; for (( i=$tmpstart; i<=$tmpend; i++ )) ; do echo $i ; done
产量:
0 1 2 3 4
如果你正在做shell命令而且你(像我一样)对流水线有一种迷恋,那么这个很好:
seq 1 $END | xargs -I {} echo {}