如何在Bash中迭代由变量定义的数字范围?

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

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

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

其中给出:

1
2
3
4
5

但是,如何用变量替换范围端点中的任何一个?这不起作用:

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

哪些打印:

{1…5}

1601937 次浏览

您可以使用

for i in $(seq $END); do echo $i; done
for i in $(seq 1 $END); do echo $i; done

edit: I prefer seq over the other methods because I can actually remember it ;)

这在bash中运行良好:

END=5i=1 ; while [[ $i -le $END ]] ; doecho $i((i = i + 1))done

讨论

使用seq没问题,正如加亚罗建议的那样。Pax Diablo建议使用Bash循环来避免调用子进程,如果$END太大,它的额外好处是内存更友好。Zathrus在循环实现中发现了一个典型的bug,并暗示由于i是一个文本变量,因此连续来回转换数字会伴随着相关的减速。

整数算术

这是Bash循环的改进版本:

typeset -i i ENDlet END=5 i=1while ((i<=END)); doecho $i…let i++done

如果我们唯一想要的是echo,那么我们可以写echo $((i++))

显影教会了我一些东西:Bash允许for ((expr;expr;expr))结构。由于我从未阅读过Bash的整个手册页(就像我对Korn shell(ksh)手册页所做的那样,那是很久以前的事了),我错过了。

所以,

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

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

最初的问题

ESCHERCROCER指出,{一个b}Bash表示法仅适用于文字;true,相应于Bash手册。可以使用单个(内部)fork()克服这个障碍,而不需要exec()(就像调用seq的情况一样,它是另一个图像,需要分叉+exec):

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

evalecho都是Bash内置的,但是命令替换需要fork()$(…)构造)。

seq方法是最简单的,但Bash有内置的算术计算。

END=5for ((i=1;i<=END;i++)); doecho $idone# ==> outputs 1 2 3 4 5 on separate lines

for ((expr1;expr2;expr3));构造的工作方式与C和类似语言中的for (expr1;expr2;expr3)类似,并且与其他((expr))情况一样,Bash将它们视为算术。

另一层间接:

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

如果您使用的是BSD/OS X,则可以使用jot而不是seq:

for i in $(jot $END); do echo $i; done

这就是为什么原始表达式不起作用。

manbash

之前执行支撑展开任何其他扩展,以及任何特殊于其他扩展保存在结果。这是严格的文本。巴斯不应用任何语法的范围内的解释之间的扩展或文本大括号

所以,支撑扩张是在参数扩展。之前作为纯文本宏操作完成的

shell是宏处理器和更正式的编程语言之间的高度优化的混合体。为了优化典型的用例,语言变得相当复杂,并且接受了一些限制。

建议

我建议坚持使用Po的1特性。这意味着使用for i in <list>; do,如果列表已经已知,否则,使用whileseq,如:

#!/bin/sh
limit=4
i=1; while [ $i -le $limit ]; doecho $ii=$(($i + 1))done# Or -----------------------for i in $(seq 1 $limit); doecho $idone


1. Bash是一个很棒的shell,我以交互方式使用它,但我不会在我的脚本中加入Bash主义。脚本可能需要更快的shell,更安全的shell,更嵌入式的shell。他们可能需要在 /bin/sh安装的任何东西上运行,然后还有所有常见的亲标准参数。记住炮弹休克, aka巴什门?

这些都很好,但seq被认为是不推荐的,大多数只适用于数字范围。

如果你把for循环用双引号括起来,当你回放字符串时,开始变量和结束变量将被取消引用,你可以把字符串直接发回BASH执行。$i需要用\'s转义,这样在发送到子shell之前就不会被评估。

RANGE_START=aRANGE_END=zecho -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的第二个实例,因此它应该适合密集型操作。

我知道这个问题是关于bash的,但是-只是为了记录-ksh93更聪明,并按预期实现了它:

$ ksh -c 'i=5; for x in {1..$i}; do echo "$x"; done'12345$ 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}

{}替换为(( ))

tmpstart=0;tmpend=4;
for (( i=$tmpstart; i<=$tmpend; i++ )) ; doecho $i ;done

产量:

01234

POSIX方式

如果您关心可移植性,请使用来自POSIX标准的示例

i=2end=5while [ $i -le $end ]; doecho $ii=$(($i+1))done

输出:

2345

没有 POSIX:

  • (( ))没有美元,尽管它是一个常见的扩展正如POSIX本身所说
  • [[[在这里就足够了。另见:Bash中单方括号和双方括号有什么区别?
  • for ((;;))
  • seq(GNU Coreutils)
  • {start..end},并且不能像上面提到的那样使用变量根据Bash手册
  • let i=i+1POSIX 7 2. Shell命令语言不包含单词let,并且在bash --posix 4.3.42上失败
  • 可能需要0美元,但我不确定。POSIX 7 2.6.4算术扩展说:

    如果shell变量x包含一个形成有效整数常量的值,可以选择包括前导加号或减号,那么算术扩展“$((x))”和“$(($x))”将返回相同的值。

    但是从字面上阅读并不意味着$((x+1))会扩展,因为x+1不是变量。

这是另一种方式:

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

如果您正在执行shell命令并且您(像我一样)对流水线有迷恋,那么这个很好:

seq 1 $END | xargs -I {} echo {}

如果你需要它的前缀,你可能会喜欢这个

 for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done

这将产生

070809101112

如果您想尽可能接近大括号表达式语法,请尝试#0函数,来自bash技巧#1

例如,以下所有操作都将执行与echo {1..10}完全相同的操作:

source range.bashone=1ten=10
range {$one..$ten}range $one $tenrange {1..$ten}range {1..10}

它试图用尽可能少的“陷阱”来支持原生bash语法:不仅支持变量,而且还防止了作为字符串提供的无效范围(例如for i in {1..a}; do echo $i; done)的经常不良行为。

其他答案在大多数情况下都有效,但它们都至少有以下一个缺点:

  • 它们中的许多使用子外壳,在某些系统上可以使用危害性能可能不可能
  • 它们中的许多依赖于外部程序。甚至seq也是一个二进制文件,必须安装才能使用,必须通过bash加载,并且必须包含您期望的程序,才能在这种情况下工作。无论是否无处不在,这不仅仅是Bash语言本身。
  • 只使用原生Bash功能的解决方案,比如@epheient,不适用于字母范围,就像{a..z};大括号扩展一样。不过,问题是关于数字的范围,所以这是一个诡计。
  • 它们中的大多数在视觉上与{1..10}大括号扩展范围语法不相似,因此使用两者的程序可能更难阅读。
  • @bobbogo的回答使用了一些熟悉的语法,但是如果$END变量不是范围另一边的有效范围“book end”,则会做一些意想不到的事情。例如,如果END=a,则不会发生错误,并且逐字值{1..a}将得到回应。这也是Bash的默认行为——它通常是意想不到的。

免责声明:我是链接代码的作者。

这适用于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 vs=${1}e=${2}if (( ${e} >= ${s} )); thenv=${s}while (( ${v} <= ${e} )); doecho ${v}((v=v+1))doneelif (( ${e} < ${s} )); thenv=${s}while (( ${v} >= ${e} )); doecho ${v}((v=v-1))donefi}
function test_num_range {num_range 1 3 | egrep "1|2|3" | assert_lc 3num_range 1 3 | head -1 | assert_eq 1num_range -1 1 | head -1 | assert_eq "-1"num_range 3 1 | egrep "1|2|3" | assert_lc 3num_range 3 1 | head -1 | assert_eq 3num_range 1 -1 | tail -1 | assert_eq "-1"}

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

太长别读:

  1. seq{..}真的很快
  2. forwhile循环很慢
  3. $( )太慢了
  4. for (( ; ; ))循环比较慢
  5. $(( ))更慢
  6. 担心内存中的N数字(seq或{…})是愚蠢的(至少100万)。

这些不是结论。你必须查看每个结论背后的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.227suser    0m0.239ssys     0m0.008s
# show that {..} is fast$ time (echo {1..1000000} | wc)1 1000000 6888896
real    0m1.778suser    0m1.735ssys     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.642suser    0m3.582ssys 0m0.057s
# show that echo is slow$ time (for i in {1..1000000} ; do echo $i; done | wc)1000000 1000000 6888896
real    0m7.480suser    0m6.803ssys     0m2.580s
$ time (for i in $(seq 1 1000000) ; do echo $i; done | wc)1000000 1000000 6888894
real    0m7.029suser    0m6.335ssys     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.391suser    0m11.069ssys     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.696suser    0m18.017ssys     0m3.806s
$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; ((i=i+1)); done | wc)1000000 1000000 6888896
real    0m18.629suser    0m16.843ssys     0m3.936s
$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $((i++)); done | wc)1000000 1000000 6888896
real    0m17.012suser    0m15.319ssys     0m3.906s
# even a noop is slow$ time (i=1; e=1000000; while [ $((i++)) -le $e ]; do :; done | wc)0       0       0
real    0m12.679suser    0m11.658ssys 0m1.004s

有很多方法可以做到这一点,但我更喜欢的是下面给出的

使用seq

故事man seq

$ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last

语法

完整命令
seq first incr last

  • first是序列中的起始数字[是可选的,默认情况下:1]
  • incr是增量[是可选的,默认情况下:1]
  • Last是序列中的最后一个数字

示例:

$ seq 1 2 101 3 5 7 9

只有第一个和最后一个:

$ seq 1 51 2 3 4 5

只有最后一个:

$ seq 51 2 3 4 5

使用{first..last..incr}

这里first和Last是强制的,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

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

a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash

PS:我的bash没有seq命令。

在Mac OSX 10.6.8、Bash 3.2.48上测试