Shell脚本中的"for"循环语法

261

我已经使以下代码可以运行:

for i in {2..10}
do
    echo "output: $i"
done

它产生一堆output: 2output: 3等等的行。

然而,尝试运行以下代码:

max=10
for i in {2..$max}
do
    echo "$i"
done

生成以下内容:

output: {2..10}

如何让编译器意识到$max是数组的另一端,而不是字符串的一部分呢?


2
你在使用什么系统和shell?有哪种愚蠢的系统有sh或bash,但没有seq这个核心工具? - whatsisname
12
自由软件基金会并未支持 FreeBSD。 - Nietzche-jou
小的风格问题:通常我会看到forif关键字与dothen关键字在同一行。例如:for i in {2..10}; do - a paid nerd
可能是在bash中的for语法中使用变量是否可行?的重复问题。 - Barmar
FreeBSD至少有10个版本都包含了/usr/bin/seq。 - jrm
@whatsisname,seq不是POSIX标准化的。即使它存在,也不能保证具有任何特定的行为。这就是为什么在http://wooledge.org/~greybot/meta/counting(freenode #bash频道事实数据库中相关条目)讨论的模式中没有一个依赖于它的原因。 - Charles Duffy
11个回答

305

花括号扩展,{x..y} 在其他扩展之前执行,因此您无法将其用于长度可变的序列。

相反,使用 seq 2 $max 方法,如用户mob所述

因此,对于您的示例,它应该是:

max=10
for i in `seq 2 $max`
do
    echo "$i"
done

没有什么好的理由使用外部命令,如 seq 来计数和增加 for 循环中的数字,因此建议避免使用 seq。该命令仅用于与旧版 bash 的兼容性。内置命令已经足够快了。 for (( EXP1; EXP2; EXP3 )) ... - miller
16
@miller,for (( ... )) 语法不是 POSIX 标准,这意味着例如在 Alpine Linux 上它不会直接起作用,而 seq 可以。 - Wernight
@Wernight 不仅是Alpine Linux; 在Debian/Ubuntu中, #!/bin/sh 是Dash。 - Franklin Yu

92

试试使用算术表达式版本的 for 循环:

max=10
for (( i=2; i <= $max; ++i ))
do
    echo "$i"
done

这在大多数版本的Bash中都可用,也应该与Bourne shell (sh)兼容。


1
@Flow:嗯,我在几个系统上尝试了一下(基于Linux和BSD),使用#!/bin/sh,它可以正常工作。在bash下调用,特别是在/bin/sh下调用,仍然可以工作。也许sh的版本很重要?你在一些旧的Unix系统上吗? - system PAUSE
8
很少有系统会专门设置 sh,而是将其作为指向其他Shell的链接。理想情况下,这样一个名为 sh 的Shell 将仅支持 POSIX 标准中的功能,但默认情况下也允许一些额外功能通过。C风格的for循环不是POSIX功能,但实际上的Shell可能在 sh 模式下支持。 - chepner

37

手动执行循环:

i=0
max=10
while [ $i -lt $max ]
do
    echo "输出:$i"
    true $(( i++ ))
done

如果您不必完全遵循POSIX标准,可以使用算术 for 循环:

max=10
for (( i=0; i < max; i++ )); do echo "输出:$i"; done

或者在BSD系统上使用jot(1):

for i in $( jot 0 10 ); do echo "输出:$i"; done

4
true $(( i++ )) 并不适用于所有情况,因此最可移植的方法是 true $((i=i+1)) - Flow
在 "for (( i=0; i < max; i++ ));" 中不应该出现分号。 - logan

19
如果你的系统上有 seq 命令可用:
for i in `seq 2 $max`
do
  echo "output: $i"
done

如果不行的话,那么可以使用“穷人版”的 seq 命令和 perl 来实现:

seq=`perl -e "\$,=' ';print 2..$max"`
for i in $seq
do
  echo "output: $i"
done

注意引号的使用。


17
我们可以像C编程一样迭代循环。
#!/bin/bash
for ((i=1; i<=20; i=i+1))
do 
      echo $i
done

9

有多种方法可以做到这一点。

max=10
for i in `eval "echo {2..$max}"`
do
    echo "$i"
done

7
由于eval将评估您设置的max任何内容,因此存在安全风险。请考虑max =“2}; echo ha;#”,然后将echo ha替换为更具破坏性的内容。 - chepner
7
她/他将最大值设为10,没有风险。 - Conrad Meyer

5
这是一种方法:
Bash:
max=10
for i in $(bash -c "echo {2..${max}}"); do echo $i; done

当用ksh -czsh -c替换bash -c时,上述Bash方法也适用于kshzsh

注意:for i in {2..${max}}; do echo $i; donezshksh中有效。


3

因为我的系统(Mac OS X v10.6.1 (Snow Leopard))没有安装seq命令,所以我只好使用while循环代替:

max=5
i=1

while [ $max -gt $i ]
do
    (stuff)
done

*耸肩* 那就用这个方法吧。


2
"seq" 相对较新。我只是在几个月前才发现它。但是你可以使用 "for" 循环!!"while" 的缺点是你必须记得在循环内的某个地方增加计数器,否则会向下循环。 - system PAUSE

2

这些都是{1..8},并且都应该符合POSIX标准。如果在循环中放置条件continue,它们也不会出错。惯用的写法:

f=
while [ $((f+=1)) -le 8 ]
do
  echo $f
done

另一种方法:
g=
while
  g=${g}1
  [ ${#g} -le 8 ]
do
  echo ${#g}
done

还有一个:

set --
while
  set $* .
  [ ${#} -le 8 ]
do
  echo ${#}
done

太棒了,我正在寻找busybox的ash解决方案,你的答案非常完美。 - kkocdko

2

这在 Mac OS X 上是可行的。

它包括了一个 BSD 日期的示例,以及如何增加和减少日期:

for ((i=28; i>=6 ; i--));
do
    dat=`date -v-${i}d -j "+%Y%m%d"` 
    echo $dat
done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接