1941

当范围由变量给出时,如何迭代 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}

4

20 回答 20

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

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

于 2008-10-04T01:41:55.673 回答
628

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

于 2008-10-04T21:43:47.913 回答
210

讨论

正如贾罗建议的那样,使用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++))

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

eval和都是echoBash 内置函数,但fork()命令替换($(…)构造)需要 a。

于 2008-10-04T02:38:48.710 回答
113

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

man bash

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

因此,大括号扩展是在参数扩展之前作为纯文本宏操作尽早完成的。

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

推荐

我建议坚持使用 Posix 1功能。这意味着使用for i in <list>; do,如果列表已知,否则使用whileor 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 的任何东西上运行,然后有所有常见的支持标准的参数。还记得shellshock,又名bashdoor 吗?

于 2011-04-19T22:32:44.227 回答
92

POSIX 方式

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

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

输出:

2
3
4
5

不是POSIX的东西:

于 2015-07-12T07:54:44.353 回答
36

另一层间接:

for i in $(eval echo {1..$END}); do
    ∶
于 2011-03-14T19:51:36.677 回答
35

您可以使用

for i in $(seq $END); do echo $i; done
于 2008-10-04T01:41:23.753 回答
24

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

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

这将产生

07
08
09
10
11
12
于 2017-06-03T20:14:38.257 回答
23

我在这里结合了一些想法并测量了性能。

TL;DR 要点:

  1. seq而且{..}真的很快
  2. for并且while循环很慢
  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.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
于 2019-02-19T16:25:17.593 回答
21

如果你在 BSD / OS X 上,你可以使用 jot 而不是 seq:

for i in $(jot $END); do echo $i; done
于 2011-03-15T23:29:16.203 回答
18

这适用于bash

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done
于 2008-10-04T01:42:39.883 回答
8

我知道这个问题是关于 的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}
于 2013-09-19T12:33:02.677 回答
8

这是另一种方式:

end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
于 2015-07-12T12:36:51.050 回答
8

如果您想尽可能接近大括号表达式语法,请尝试rangebash-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还防止了作为字符串(例如)提供的无效范围的常见不良行为。

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

  • 他们中的许多人使用subshel​​ls,这可能会损害性能,并且在某些系统上可能是不可能的。
  • 他们中的许多人依赖外部程序。Evenseq是一个必须安装才能使用的二进制文件,必须由 bash 加载,并且必须包含您期望的程序,才能在这种情况下工作。不管是否无处不在,这不仅仅依赖于 Bash 语言本身。
  • 仅使用本机 Bash 功能的解决方案,例如 @ephemient 的,将不适用于字母范围,例如{a..z}; 大括号展开会。不过,问题是关于数字范围的,所以这是一个小问题。
  • 它们中的大多数在视觉上与{1..10}大括号扩展范围语法并不相似,因此使用两者的程序可能会有点难以阅读。
  • @bobbogo 的答案使用了一些熟悉的语法,但如果$END变量不是范围另一侧的有效范围“书挡”,则会出现意想不到的情况。例如,如果END=a不会发生错误,则会{1..a}回显逐字记录值。这也是 Bash 的默认行为——只是经常出乎意料。

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

于 2017-07-27T13:30:48.167 回答
8

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

使用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 10
1 3 5 7 9

只有第一个和最后一个:

$ seq 1 5
1 2 3 4 5

只有最后一个:

$ seq 5
1 2 3 4 5

使用{first..last..incr}

这里 first 和 last 是强制性的,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
于 2019-05-27T16:41:10.087 回答
7

这些都很好,但 seq 据说已被弃用,并且大多数仅适用于数字范围。

如果你用双引号将你的 for 循环括起来,当你回显字符串时,开始和结束变量将被取消引用,你可以将字符串直接发送回 BASH 执行。$i需要用 \'s 转义,因此在发送到子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 的第二个实例,因此它应该适合密集型操作。

于 2011-08-16T21:18:30.490 回答
7

替换{}(( ))

tmpstart=0;
tmpend=4;

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

产量:

0
1
2
3
4
于 2014-03-12T00:10:10.067 回答
6

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

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

于 2017-03-27T19:32:05.023 回答
3

如果您不想使用 ' 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

seqPS:我的 bash无论如何都没有 ' ' 命令。

在 Mac OSX 10.6.8、Bash 3.2.48 上测试

于 2019-06-18T16:54:45.750 回答
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"
}
于 2018-04-11T02:37:36.523 回答