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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/169511/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:43:30  来源:igfitidea点击:

How do I iterate over a range of numbers defined by variables in Bash?

bashshellfor-loopsyntax

提问by eschercycle

How do I iterate over a range of numbers in Bash when the range is given by a variable?

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

I know I can do this (called "sequence expression" in the Bash documentation):

我知道我可以做到这一点(在 Bash文档中称为“序列表达式” ):

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

Which gives:

这使:

1
2
3
4
5

1
2
3
4
5

Yet, how can I replace either of the range endpoints with a variable? This doesn't work:

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

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

Which prints:

哪个打印:

{1..5}

{1..5}

回答by Jiaaro

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

edit: I prefer seqover the other methods because I can actually remember it ;)

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

回答by ephemient

The seqmethod is the simplest, but Bash has built-in arithmetic evaluation.

seq方法最简单,但 Bash 具有内置的算术评估。

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines

The for ((expr1;expr2;expr3));construct works just like for (expr1;expr2;expr3)in C and similar languages, and like other ((expr))cases, Bash treats them as arithmetic.

for ((expr1;expr2;expr3));构造的工作原理与for (expr1;expr2;expr3)C 和类似语言中的一样,并且与其他((expr))情况一样,Bash 将它们视为算术。

回答by tzot

discussion

讨论

Using seqis fine, as Jiaaro suggested. Pax Diablo suggested a Bash loop to avoid calling a subprocess, with the additional advantage of being more memory friendly if $END is too large. Zathrus spotted a typical bug in the loop implementation, and also hinted that since iis a text variable, continuous conversions to-and-fro numbers are performed with an associated slow-down.

seq正如嘉罗建议的那样,使用很好。Pax Diablo 建议使用 Bash 循环来避免调用子进程,另外的好处是如果 $END 太大则内存更友好。Zathrus 在循环实现中发现了一个典型的错误,并且还暗示由于它i是一个文本变量,所以来回数字的连续转换会伴随着相关的减速执行。

integer arithmetic

整数运算

This is an improved version of the Bash loop:

这是 Bash 循环的改进版本:

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    …
    let i++
done

If the only thing that we want is the echo, then we could write echo $((i++)).

如果我们唯一想要的是echo,那么我们可以编写echo $((i++)).

ephemienttaught me something: Bash allows for ((expr;expr;expr))constructs. Since I've never read the whole man page for Bash (like I've done with the Korn shell (ksh) man page, and that was a long time ago), I missed that.

ephemient教会了我一些东西:Bash 允许for ((expr;expr;expr))构造。因为我从来没有读过 Bash 的整个手册页(就像我读过 Korn shell ( ksh) 手册页一样,那是很久以前的事了),我错过了。

So,

所以,

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

seems to be the most memory-efficient way (it won't be necessary to allocate memory to consume seq's output, which could be a problem if END is very large), although probably not the “fastest”.

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

the initial question

最初的问题

eschercycle noted that the {a..b} Bash notation works only with literals; true, accordingly to the Bash manual. One can overcome this obstacle with a single (internal) fork()without an exec()(as is the case with calling seq, which being another image requires a fork+exec):

eschercycle 指出 { a.. b} Bash 表示法仅适用于文字;是的,根据 Bash 手册。一个人可以用一个(内部)fork()没有一个exec()(就像调用seq,另一个图像需要一个 fork+exec)来克服这个障碍:

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

Both evaland echoare Bash builtins, but a fork()is required for the command substitution (the $(…)construct).

二者evalecho都是Bash建宏,但是fork()是所必需的命令取代(所述$(…)构建体)。

回答by DigitalRoss

Here is why the original expression didn't work.

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

From man bash:

人 bash

Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result. It is strictly textual. Bash does not apply any syntactic interpretation to the context of the expansion or the text between the braces.

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

So, brace expansionis something done early as a purely textual macro operation, before parameter expansion.

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

Shells are highly optimized hybrids between macro processors and more formal programming languages. In order to optimize the typical use cases, the language is made rather more complex and some limitations are accepted.

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

Recommendation

推荐

I would suggest sticking with Posix1features. This means using for i in <list>; do, if the list is already known, otherwise, use whileor seq, as in:

我建议坚持使用 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 is a great shell and I use it interactively, but I don't put bash-isms into my scripts. Scripts might need a faster shell, a more secure one, a more embedded-style one. They might need to run on whatever is installed as /bin/sh, and then there are all the usual pro-standards arguments. Remember shellshock,aka bashdoor?1. Bash 是一个很棒的 shell,我以交互方式使用它,但我没有将 bash-isms 放入我的脚本中。脚本可能需要更快的外壳、更安全的外壳、更具嵌入式风格的外壳。它们可能需要在安装为 /bin/sh 的任何内容上运行,然后还有所有常用的专业标准参数。还记得shellshock,又名bashdoor 吗?

回答by bobbogo

Another layer of indirection:

另一层间接:

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

回答by Peter Hoffmann

You can use

您可以使用

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

回答by hossbear

If you need it prefix than you might like this

如果你需要它前缀比你可能喜欢这个

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

that will yield

那会产生

07
08
09
10
11
12

回答by jefeveizen

If you're on BSD / OS X you can use jot instead of seq:

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

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

回答by paxdiablo

This works fine in bash:

这适用于bash

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

回答by Bruno Bronosky

I've combined a few of the ideas here and measured performance.

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

TL;DR Takeaways:

TL; DR 要点:

  1. seqand {..}are really fast
  2. forand whileloops are slow
  3. $( )is slow
  4. for (( ; ; ))loops are slower
  5. $(( ))is even slower
  6. Worrying about Nnumbers in memory (seq or {..}) is silly (at least up to 1 million.)
  1. seq而且{..}真的很快
  2. for并且while循环很慢
  3. $( )是慢的
  4. for (( ; ; ))循环较慢
  5. $(( ))甚至更慢
  6. 担心内存中的N 个数字(seq 或 {..})是愚蠢的(至少高达 100 万。)

These are not conclusions. You would have to look at the C code behind each of these to draw conclusions. This is more about how we tend to use each of these mechanisms for looping over code. Most single operations are close enough to being the same speed that it's not going to matter in most cases. But a mechanism like for (( i=1; i<=1000000; i++ ))is many operations as you can visually see. It is also many more operations per loopthan you get from for i in $(seq 1 1000000). And that may not be obvious to you, which is why doing tests like this is valuable.

这些都不是结论。您必须查看每一个背后的 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