题 如何迭代Bash中变量定义的一系列数字?


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

我知道我可以这样做(在Bash中称为“序列表达”) 文件):

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

这使:

1
  2
  3
  4
  五

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

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

哪个印刷品:

{} 1..5


1058
2017-10-04 01:38


起源


大家好,我在这里阅读的信息和提示都非常有用。我认为最好避免使用seq。原因是某些脚本需要是可移植的,并且必须在各种unix系统上运行,其中某些命令可能不存在。举个例子,在FreeBSD系统上默认不存在seq。
相关讨论: bash for loop:一系列数字 和 unix.stackexchange.com - 在bash中,是否可以在for循环的循环控件中使用整数变量? - blong
我不记得究竟是哪个版本的Bash,但是这个命令也支持尾随零。这有时真的很有帮助。命令 for i in {01..10}; do echo $i; done 会给出类似的数字 01, 02, 03, ..., 10。 - topr
对于像我这样只想迭代一系列索引的人 排列,bash的方式是: myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done (注意感叹号)。它比原始问题更具体,但可以提供帮助。看到 bash参数扩展 - PlasmaBinturong


答案:


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

编辑:我更喜欢 seq 其他方法,因为我实际上可以记住它;)


1190
2017-10-04 01:41



seq涉及执行外部命令,这通常会减慢速度。这可能无关紧要,但如果您正在编写脚本来处理大量数据,这一点就变得非常重要。 - paxdiablo
对于单线程来说很好。 Pax的解决方案也很好,但如果性能真的是一个问题我不会使用shell脚本。 - eschercycle
seq只调用一次以生成数字。除非这个循环在另一个紧密循环中,否则exec()的重要性不应该很大。 - Javier
外部命令并不真正相关:如果您担心运行外部命令的开销,您根本不想使用shell脚本,但通常在unix上,开销很低。但是,如果END很高,则存在内存使用问题。 - Mark Baker
注意 seq $END 就足够了,因为默认是从1开始 man seq:“如果省略FIRST或INCREMENT,则默认为1”。 - fedorqui


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将它们视为算术。


310
2017-10-04 21:43



一个人不断学习。我会先加一个 typeset -i i END 虽然。在预打击时间(即ksh),它产生了不同,但计算机慢得多。 - tzot
这样可以避免大型列表的内存开销和依赖性 seq。用它! - bobbogo
对我来说是最好的解决方案,因为我需要它来在不同的操作系统上进 - Hachi
@MarinSagovac这个 不 工作,没有语法错误。你确定你的shell是Bash吗? - gniourf_gniourf
@MarinSagovac一定要做 #!/bin/bash 脚本的第一行。 wiki.ubuntu.com/... - Melebius


讨论

运用 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)man page,那是很久以前的事了),我错过了。

所以,

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

似乎是最节省内存的方式(没有必要分配内存来消费 seq的输出,如果END非常大,这可能是一个问题),虽然可能不是“最快的”。

最初的问题

eschercycle注意到{一个..bBash表示法仅适用于文字;是的,相应于Bash手册。人们可以通过一个(内部)克服这个障碍 fork() 没有 exec() (就像打电话一样 seq,这是另一个图像需要一个fork + exec):

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

eval 和 echo 是Bash内置的,但是 fork() 是命令替换所必需的( $(…) 构造)。


160
2017-10-04 02:38



C样式循环的唯一缺点是它不能使用命令行参数,因为它们以“$”开头。 - karatedog
@karatedog: for ((i=$1;i<=$2;++i)); do echo $i; done 在bash v.4.1.9中,脚本在我身上运行正常,所以我没有看到命令行参数的问题。你的意思是别的吗? - tzot
似乎eval解决方案比C语言中的内置更快:$(时间为(i = 1; i <= 100000; ++ i));做:;完成真实0m21.220s用户0m19.763s系统0m1.203s $时间为$ in $(eval echo“{1..100000}”);做:;完成;实际0m13.881s用户0m13.536s sys 0m0.152s - Marcin Zaluski
对,但是 eval是邪恶的...... @MarcinZaluski time for i in $(seq 100000); do :; done 更快! - F. Hauri
性能必须是特定于平台的,因为eval版本在我的机器上最快。 - Andrew Prock


这就是原始表达不起作用的原因。

男人重击

之前进行了支撑扩展   任何其他扩展,以及任何其他扩展   其他特殊字符   扩展保留在   结果。这是严格的文字。巴什   不适用任何句法   解释的背景   扩展或文本之间   括号。

所以, 支撑扩张 在此之前,它是一种纯粹的文本宏操作 参数扩展。

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

建议

我建议坚持使用Posix1 特征。这意味着使用 for i in <list>; 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


1. Bash是一个很棒的shell,我以交互方式使用它,但我没有将bash-isms放入我的脚本中。脚本可能需要更快的shell,更安全的shell,更嵌入式的shell。他们可能需要运行在/ bin / sh上安装的任何东西,然后有所有通常的专业标准参数。记得 弹震, 又名 bashdoor? 


81
2018-04-19 22:32



我没有这种力量,但是我会将这一点移动到列表中,首先是bash肚脐凝视,但是在C风格的循环和算术评估之后立即进行。 - mateor
这意味着与大括号相比,大括号扩展不会节省太多内存 seq 对于大范围。例如。, echo {1..1000000} | wc 揭示回声产生1行,100万字和6,888,896字节。试 seq 1 1000000 | wc 产生一百万行,一百万字,和6,888,896字节,并且速度也快七倍以上 time 命令。 - George
注意:我之前提到过POSIX while 先前在我的回答中的方法: stackoverflow.com/a/31365662/895245 但很高兴你同意:-) - Ciro Santilli 新疆改造中心 六四事件 法轮功


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+1POSIX 7 2. Shell命令语言 不包含这个词 let,它失败了 bash --posix 42年3月4日
  • 美元在 i=$i+1 可能是必需的,但我不确定。 POSIX 7 2.6.4算术扩展 说:

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

    但从字面上读它并不意味着 $((x+1)) 从那时起扩大 x+1 不是变量。


43
2017-07-12 07:54



在这个答案中只有4个赞成,这是非常不寻常的。如果这是在某个链接聚合网站上发布的,请给我一个链接,干杯。 - Ciro Santilli 新疆改造中心 六四事件 法轮功
报价指的是 x而不是整个表达。 $((x + 1)) 很好。 - chepner
虽然不便携,但与GNU不同 seq (BSD seq 允许您使用设置序列终止字符串 -t),FreeBSD和NetBSD也有 seq 分别来自9.0和3.0。 - Adrian Günter
@CiroSantilli @chepner $((x+1)) 和 $((x + 1)) 解析完全相同,就像解析器标记时一样 x+1 它将被分成3个令牌: x, +,和 1。 x 不是有效的数字标记,但它是一个有效的变量名称标记 x+ 不是,因此分裂。 + 是一个有效的算术运算符令牌 +1 不是,所以令牌再次分裂。等等。 - Adrian Günter


另一层间接:

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

28
2018-03-14 19:51



+1:此外,在{1 ..'$ END'}中,我为eval';做......'eval似乎是解决这个问题的自然方式。 - William Pursell


您可以使用

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

21
2017-10-04 01:41



它不涉及为每次迭代执行外部命令,只需执行一次。如果启动一个外部命令的时间是个问题,那么您使用的是错误的语言。 - Mark Baker
+1为 $() 而不是反叛 - Dennis Williamson
为什么$()比``更好? - Sqeaky
@Sqeaky:你有没有试过窝 `` 电话 - ?) - tzot
嵌套这个问题的唯一案例是什么?我想知道是否存在性能差异或某些未知的技术副作用? - Sqeaky