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

时间:2008-10-04 01:38:44

标签: bash shell for-loop syntax

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

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

 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}

20 个答案:

答案 0 :(得分:1424)

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

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

答案 1 :(得分:366)

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

答案 2 :(得分:172)

讨论

使用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)手册页那样,那是很久以前的事了),我错过了。

所以,

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内置函数,但命令替换需要fork()$(…)构造)。

答案 3 :(得分:94)

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

来自 man bash

  

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

因此,在参数扩展之前,大括号扩展是一种纯粹的文本宏操作。

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

  

建议

我建议坚持使用Posix 1 功能。这意味着使用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放入我的脚本中。脚本可能需要更快的shell,更安全的shell,更嵌入式的shell。他们可能需要运行在/ bin / sh上安装的任何东西,然后有所有通常的专业标准参数。还记得 shellshock,又名 bashdoor?

答案 4 :(得分:60)

POSIX方式

如果您关心可移植性,请使用example from the POSIX standard

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

输出:

2
3
4
5

POSIX:

的事情

答案 5 :(得分:31)

另一层间接:

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

答案 6 :(得分:22)

您可以使用

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

答案 7 :(得分:18)

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

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

答案 8 :(得分:18)

如果你需要它的前缀比你想要的那样

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

将产生

07
08
09
10
11
12

答案 9 :(得分:15)

这在bash

中工作正常
END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done

答案 10 :(得分:7)

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

$ 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}

答案 11 :(得分:7)

如果您希望尽可能接近大括号表达式语法,请试用range function from bash-tricks' range.bash

例如,以下所有内容与echo {1..10}完全相同:

source range.bash
one=1
ten=10

range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}

它试图用尽可能少的“陷阱”来支持本机bash语法:不仅支持变量,而且作为字符串提供的无效范围的常常不合需要的行为(例如for i in {1..a}; do echo $i; done)被阻止为好。

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

  • 他们中的许多人使用subshells,在某些系统上可以harm performancemay not be possible
  • 他们中的许多人依赖外部程序。甚至seq是必须安装才能使用的二进制文件,必须由bash加载,并且必须包含您期望的程序,以便在这种情况下工作。无论是否普遍存在,除了Bash语言本身之外,还有更多的东西可以依赖。
  • 仅使用本机Bash功能的解决方案(如@ ephemient)不适用于字母范围,例如{a..z};支撑扩张将。问题是关于数字的范围,所以这是一个狡辩。
  • 它们中的大多数在视觉上与{1..10}大括号扩展范围语法不相似,因此使用两者的程序可能更难以阅读。
  • @ bobbogo的答案使用了一些熟悉的语法,但如果$END变量不是范围另一侧的有效范围“书挡”,则会出现意外情况。例如,如果END=a,则不会发生错误,并且将回显逐字值{1..a}。这也是Bash的默认行为 - 它通常是意料之外的。

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

答案 12 :(得分:6)

{}替换为(( ))

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done

收率:

0
1
2
3
4

答案 13 :(得分:6)

我在这里结合了一些想法并评估了性能。

TL; DR外带:

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

这些不是结论。您必须查看每一个背后的C代码才能得出结论。这更多地是关于我们如何倾向于使用这些机制中的每一个来循环代码。大多数单次操作的速度足以接近大多数情况下无关紧要的速度。但是像for (( i=1; i<=1000000; i++ ))这样的机制有很多操作,如您所见。与您从for i in $(seq 1 1000000)获得的操作相比,每个循环要多得多的操作。这对您来说可能并不明显,这就是为什么进行这样的测试很有价值。

Demos

# 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

答案 14 :(得分:6)

这是另一种方式:

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

答案 15 :(得分:6)

这些都很好但是seq应该被弃用,大多数只能使用数值范围。

如果用双引号括起for循环,则在回显字符串时将取消引用开始和结束变量,并且可以将字符串右回送到BASH执行。 $i需要使用\来进行转义,因此在发送到子shell之前不会对其进行评估。

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

答案 16 :(得分:5)

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

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

答案 17 :(得分:0)

这适用于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 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"
}

答案 18 :(得分:0)

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

使用seq

  

man seq的简介

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

语法

完整命令
seq first incr last

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

示例:

$ seq 1 2 10
1 3 5 7 9

只有第一个和最后一个:

$ seq 1 5
1 2 3 4 5

只有最后一个:

$ seq 5
1 2 3 4 5

使用{first..last..incr}

在此首先和最后是强制性的,而incr是可选的

仅使用第一个和最后一个

$ echo {1..5}
1 2 3 4 5

使用增量

$ 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

答案 19 :(得分:0)

如果您不想使用'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上进行了测试