如何遍历 Bash 中变量定义的数字范围?

当变量给定范围时,如何在 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}

答案

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));构造的工作方式与 C 和类似语言中的for (expr1;expr2;expr3)一样,并且与其他((expr))情况一样,Bash 将其视为算术运算。

讨论区

正如 Jiaaro 所建议的,使用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++))

短暂性教会了我一些东西: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

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

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

来自man bash

括号扩展在执行任何其他扩展之前执行,并且其他扩展专用的任何字符都保留在结果中。严格来说是文字。 Bash 对扩展的上下文或大括号之间的文本不应用任何语法解释。

因此,在参数扩展之前, 大括号扩展是纯文本宏操作的早期操作

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

建议

我建议您坚持使用 Posix 1功能。这意味着for i in <list>; do使用for i in <list>; do如果已知道列表,则for i in <list>; do ,否则,使用whileseq ,如:

#!/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 放入我的脚本中。脚本可能需要更快的外壳,更安全的外壳,更嵌入式的外壳。他们可能需要在安装为 / bin / sh 的任何设备上运行,然后有所有常用的 pro-standards 参数。还记得shellshock,又名bashdoor 吗?

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失败bash --posix 4.3.42
  • i=$i+1处的美元可能是必需的,但我不确定。 POSIX 7 2.6.4 算术扩展说:

    如果外壳变量 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