How do I iterate over a range of numbers defined by variables in Bash?
当范围由变量给出时,如何在bash中迭代一个数字范围?
我知道我可以这样做(在bash文档中称为"序列表达式"):
1 | for i in {1..5}; do echo $i; done |
它给出:
1
2
3
4
5
但是,如何用变量替换任意一个范围端点?这不起作用:
1 2 | END=5 for i in {1..$END}; do echo $i; done |
哪些印刷品:
{1..5}
1 | for i in $(seq 1 $END); do echo $i; done |
编辑:我比其他方法更喜欢
1 2 3 4 5 | END=5 for ((i=1;i<=END;i++)); do echo $i done # ==> outputs 1 2 3 4 5 on separate lines |
讨论
如加亚罗所建议的,使用
这是bash循环的改进版本:
1 2 3 4 5 6 7 | typeset -i i END let END=5 i=1 while ((i<=END)); do echo $i … let i++ done |
如果我们唯一想要的是
以弗米特教了我一些东西:bash允许
所以,
1 2 | typeset -i i END # Let's be explicit for ((i=1;i<=END;++i)); do echo $i; done |
似乎是内存效率最高的方法(不需要分配内存来消耗
Eschercycle注意到,a..b bash符号仅适用于文本;与bash手册相应,为真。一个人可以通过一个没有
1 | for i in $(eval echo"{1..$END}"); do |
这就是为什么最初的表达方式不起作用。
从人巴什:
Brace expansion is performed before
any other expansions, and any
characters special to other
expansions are preserved in the
result. It is strictly textual. Bash
does not apply any syntactic
interpretation to the context of
the expansion or the text between the
braces.
所以,在参数展开之前,大括号展开是一个纯文本的宏操作。
shell是宏处理器和更正式的编程语言之间高度优化的混合体。为了优化典型的用例,语言变得更加复杂,并且接受了一些限制。
Recommendation
我建议继续使用posix1特性。这意味着使用; do
1 2 3 4 5 6 7 8 9 10 11 12 | #!/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的设备上运行,然后就有了所有常见的pro-standards参数。还记得Shellshock,又名bashdoor吗?
POSIX方式
如果您关心可移植性,请使用POSIX标准中的示例:
1 2 3 4 5 6 | i=2 end=5 while [ $i -le $end ]; do echo $i i=$(($i+1)) done |
输出:
1 2 3 4 | 2 3 4 5 |
不是posix的东西:
- 没有美元的
(( )) ,尽管这是posix本身提到的一个常见扩展。 [[ 。这里就足够了。另请参见:bash中的单方括号和双方括号有什么区别?for ((;;)) seq (GNU核心用户){start..end} ,不能使用bash手册中提到的变量。let i=i+1 :位置7 2。shell命令语言不包含单词let ,在bash --posix 4.3.42上失败。可能需要在EDOCX1[9]兑换美元,但我不确定。posix 7 2.6.4算术展开表示:
If the shell variable x contains a value that forms a valid integer constant, optionally including a leading plus or minus sign, then the arithmetic expansions"$((x))" and"$(($x))" shall return the same value.
但从字面上看,这并不意味着
$((x+1)) 会扩大,因为x+1 不是一个变量。
另一层间接性:
1 2 | for i in $(eval echo {1..$END}); do ∶ |
你可以使用
1 | for i in $(seq $END); do echo $i; done |
如果您在bsd/os x上,可以使用jot而不是seq:
1 | for i in $(jot $END); do echo $i; done |
如果你需要它的前缀,你可能会喜欢这个
1 2 | for ((i=7;i<=12;i++)); do echo `printf"%2.0d " $i |sed"s/ /0/"`;done |
这将产生
1 2 3 4 5 6 | 07 08 09 10 11 12 |
这在
1 2 3 4 5 | END=5 i=1 ; while [[ $i -le $END ]] ; do echo $i ((i = i + 1)) done |
我知道这个问题是关于
1 2 3 4 5 6 7 8 9 10 11 | $ 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} |
这是另一种方式:
1 2 | end=5 for i in $(bash -c"echo {1..${end}}"); do echo $i; done |
如果您希望尽可能接近大括号表达式语法,请尝试bash技巧的
例如,以下所有操作与
1 2 3 4 5 6 7 8 | source range.bash one=1 ten=10 range {$one..$ten} range $one $ten range {1..$ten} range {1..10} |
它试图用尽可能少的"gotchas"来支持本机bash语法:不仅支持变量,而且还防止了作为字符串(例如
其他答案在大多数情况下都有效,但它们都至少有以下缺点之一:
- 它们中的许多使用子shell,这可能会损害性能,在某些系统上可能是不可能的。
- 它们中的许多依赖于外部程序。即使是
seq 也是一个二进制文件,必须安装才能使用,必须由bash加载,并且必须包含您期望的程序,以便在这种情况下工作。无论无处不在与否,这比bash语言本身更值得依赖。 - 只使用本机bash功能的解决方案,如@ephemient,将无法在字母范围内工作,如
{a..z} ;括号扩展将起作用。不过,问题是关于数字的范围,所以这是个骗局。 - 它们中的大多数在视觉上与
{1..10} 大括号扩展范围语法不同,因此使用这两种语法的程序可能有一点难以阅读。 - @Bobbogo的回答使用了一些熟悉的语法,但如果
$END 变量不是范围另一端的有效范围"bookend",则会发生意外。例如,如果END=a ,则不会发生错误,并且将回送逐字值{1..a} 。这也是bash的默认行为——这通常是出乎意料的。
免责声明:我是链接代码的作者。
这些都很好,但seq被认为是不推荐使用的,大多数只适用于数值范围。
如果您将for循环用双引号括起来,那么当您回送字符串时,起始变量和结束变量将被取消引用,并且您可以将字符串直接送回bash执行。
1 2 3 | RANGE_START=a RANGE_END=z echo -e"for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash |
此输出也可以分配给变量:
1 | VAR=`echo -e"for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash` |
它应该生成的唯一"开销"应该是bash的第二个实例,因此它应该适合于密集的操作。
用
1 2 3 4 5 6 | tmpstart=0; tmpend=4; for (( i=$tmpstart; i<=$tmpend; i++ )) ; do echo $i ; done |
产量:
1 2 3 4 5 | 0 1 2 3 4 |
如果您正在执行shell命令,并且您(像我一样)对管道有一种迷恋,那么这个命令很好:
我结合了这里的一些想法并测量了性能。
Takeways博士:这些不是结论。为了得出结论,您必须查看每个代码后面的C代码。这更多的是关于我们如何使用这些机制中的每一个来循环代码。大多数单次操作的速度都很接近,在大多数情况下都不重要。但是像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | # 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和korn中有效,也可以从高到低的数字。可能不是最快或最漂亮,但效果很好。也可以处理底片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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" } |