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

esc*_*cle 1409 syntax bash shell for-loop

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

我知道我可以这样做(在Bash 文档中称为"序列表达式" ):

 for i in {1..5}; do echo $i; done
Run Code Online (Sandbox Code Playgroud)

这使:

1
2
3
4
5

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

END=5
for i in {1..$END}; do echo $i; done
Run Code Online (Sandbox Code Playgroud)

哪个印刷品:

{} 1..5

Jia*_*aro 1578

for i in $(seq 1 $END); do echo $i; done
Run Code Online (Sandbox Code Playgroud)

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

  • 只需一个班轮就可以了.Pax的解决方案也很好,但如果性能真的是一个问题我不会使用shell脚本. (35认同)
  • seq涉及执行外部命令,这通常会减慢速度.这可能无关紧要,但如果您正在编写脚本来处理大量数据,这一点就变得很重要了. (33认同)
  • 外部命令并不真正相关:如果你担心运行外部命令的开销,你根本不想使用shell脚本,但通常在unix上,开销很低.但是,如果END很高,则存在内存使用问题. (27认同)
  • 注意`seq $ END`就足够了,因为默认是从1开始.从`man seq`:"如果省略FIRST或INCREMENT,它默认为1". (16认同)
  • seq只调用一次以生成数字.除非这个循环在另一个紧密的循环中,否则exec()不应该是重要的. (15认同)
  • 请注意,在bash中,`{4..1}`给出'4 3 2 1',因为负的"增量"是自动推断出来的.相反,`seq 4 1`什么都不给,因为默认增量为1.在这种情况下,将使用完整命令:`seq 4 -1 1`. (5认同)
  • 如果您需要格式化数字(例如 001、002 等),您可以使用 -f 选项进行序列: $(seq -f %03.0f 1 100) (3认同)
  • 好点子。我发现自己正在构建一个快速的 ksh 脚本,它最终变成了一个怪物,鉴于我已经花费的时间,我不想重写它 - 但这可能只是我。 (2认同)

eph*_*ent 423

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

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

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

  • 这样可以避免大型列表的内存开销,以及对`seq`的依赖.用它! (56认同)
  • 一个人不断地学习。不过,我会在前面加上“typeset -ii END”。在 bash 之前的时代(即 ksh),它产生了影响,但当时的计算机速度要慢得多。 (2认同)
  • @MarinSagovac这个_does_工作,没有语法错误.你确定你的shell是Bash吗? (2认同)
  • @MarinSagovac确保将`#!/ bin/bash`作为脚本的第一行.https://wiki.ubuntu.com/DashAsBinSh#My_production_system_has_broken_and_I_just_want_to_get_it_back_up.21 (2认同)
  • 只是一个非常简短的问题:为什么((i = 1; i <= END; i ++))和NOT((i = 1; i <= $ END; i ++)); 为什么在END之前没有$? (2认同)
  • @Baedsch:由于同样的原因,我不被用作$ i。bash手册页用于算术评估的状态:“在表达式中,也可以使用名称来引用shell变量,而无需使用参数扩展语法。” (2认同)
  • 我已将此答案包含在下面的性能比较答案中。/sf/answers/3833956381/(这是给我自己的笔记,用于跟踪我还剩下哪些要做的事情。)我创建了这个答案,专门解决@bobbogo 的不合格声明和那些赞成它的人。剧透:大列表的内存开销几乎没有 bash 中 c 样式循环的缓慢性能那么糟糕。如果您有想法,请在那里发表评论。我们不要劫持这个线程。 (2认同)

tzo*_*zot 179

讨论

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
Run Code Online (Sandbox Code Playgroud)

如果我们唯一想要的是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
Run Code Online (Sandbox Code Playgroud)

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

最初的问题

eschercycle注意到{ a .. b } Bash表示法仅适用于文字; 是的,相应于Bash手册.人们可以用一个fork()没有的单个(内部)克服这个障碍exec()(就像调用的情况一样,seq另一个图像需要fork + exec):

for i in $(eval echo "{1..$END}"); do
Run Code Online (Sandbox Code Playgroud)

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

  • @karatedog:`for((i = $ 1; i <= $ 2; ++ i)); 回声$ i; 在一个脚本中完成`在bash v.4.1.9上对我来说很好,所以我没有看到命令行参数的问题.你的意思是别的吗? (3认同)
  • 是的,但*eval是邪恶的*... @MarcinZaluski`我在$(seq 100000)的时间; 做:; 完成`更快! (3认同)

Dig*_*oss 97

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

来自man bash:

在任何其他扩展之前执行大括号扩展,并且在结果中保留对其他扩展特殊的任何字符.这是严格的文字.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
Run Code Online (Sandbox Code Playgroud)


1. Bash是一个很棒的shell,我以交互方式使用它,但我没有将bash-isms放入我的脚本中.脚本可能需要更快的shell,更安全的shell,更嵌入式的shell.他们可能需要运行在/ bin/sh上安装的任何东西,然后有所有通常的专业标准参数.还记得shellshock,又名bashdoor?

  • 我没有这种力量,但是我会把这一点移到列表上,首先是bash肚脐凝视,但是在C风格的循环和算术评估之后立即进行. (13认同)
  • 这意味着与大范围的"seq"相比,大括号扩展不会节省太多内存.例如,`echo {1..1000000} | wc`显示回声产生1行,1百万字和6,888,896字节.尝试`seq 1 1000000 | wc`产生一百万行,一百万字和6,888,896字节,并且通过`time`命令测量也快七倍以上. (2认同)

Cir*_*四事件 66

POSIX的方式

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

i=2
end=5
while [ $i -le $end ]; do
    echo $i
    i=$(($i+1))
done
Run Code Online (Sandbox Code Playgroud)

输出:

2
3
4
5
Run Code Online (Sandbox Code Playgroud)

事情是不是 POSIX:


bob*_*ogo 32

另一层间接:

for i in $(eval echo {1..$END}); do
    ?
Run Code Online (Sandbox Code Playgroud)

  • +1:此外,在{1 ..'$ END'}中,我为eval'; 做......'eval似乎是解决这个问题的自然方式. (2认同)

Pet*_*ann 24

您可以使用

for i in $(seq $END); do echo $i; done
Run Code Online (Sandbox Code Playgroud)

  • @Sqeaky:你有没有试过嵌套`\`\``来电? - ) (8认同)
  • 它不涉及为每次迭代执行外部命令,只需执行一次.如果启动一个外部命令的时间是个问题,那么您使用的是错误的语言. (7认同)
  • 为什么$()比``更好? (2认同)

小智 19

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

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

会产生

07
08
09
10
11
12
Run Code Online (Sandbox Code Playgroud)

  • 不会'printf'%02d \n"$ i`比'printf'%2.0d \n"$ i | sed"s// 0 /"`更容易? (4认同)

小智 18

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

for i in $(jot $END); do echo $i; done
Run Code Online (Sandbox Code Playgroud)


pax*_*blo 16

这适用于bash:

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done
Run Code Online (Sandbox Code Playgroud)

  • `echo $((i ++))`工作并将它组合成一行. (6认同)

Bru*_*sky 10

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

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)。这对您来说可能并不明显,这就是进行这样的测试很有价值的原因。

演示版

# 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
Run Code Online (Sandbox Code Playgroud)


Adr*_*rth 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}
Run Code Online (Sandbox Code Playgroud)


Jah*_*hid 7

这是另一种方式:

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

  • 这会产生另一个 shell 的开销。 (2认同)
  • 实际上,这特别可怕,因为当 1 个就足够时它会生成 2 个炮弹。 (2认同)

Zac*_*c B 7

如果您想尽可能地与brace-expression语法保持一致,请尝试range从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}
Run Code Online (Sandbox Code Playgroud)

它试图以尽可能少的“陷阱”来支持本机bash语法:不仅支持变量,而且for i in {1..a}; do echo $i; done还防止了无效范围通常以字符串(例如)形式提供的不良行为。

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

  • 它们中的许多使用子外壳,这会损害性能,并且在某些系统上可能无法实现
  • 其中许多依赖于外部程序。Even seq是必须安装才能使用的二进制文件,必须由bash加载,并且必须包含您期望的程序,这样它才能在这种情况下起作用。不论是否泛滥,它所依赖的不仅仅是Bash语言本身。
  • 仅使用本地Bash功能(例如@ephemient的功能)的解决方案将不适用于字母范围,例如{a..z}; 大括号扩展会。但是,问题是关于数字的范围,所以这是一个小问题。
  • 它们中的大多数在外观上都不与{1..10}大括号扩展范围语法相似,因此使用这两种语法的程序可能会更难阅读。
  • @bobbogo的答案使用了一些熟悉的语法,但是如果$END变量不是该范围另一侧的有效范围“ bookend”,则会发生意外情况。END=a例如,如果使用,则不会发生错误,并且{1..a}将回显逐字记录值。这也是Bash的默认行为-经常是出乎意料的。

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


小智 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
Run Code Online (Sandbox Code Playgroud)

此输出也可以分配给变量:

VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash`
Run Code Online (Sandbox Code Playgroud)

这应该生成的唯一"开销"应该是bash的第二个实例,因此它应该适用于密集操作.


小智 6

替换{}(( )):

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done
Run Code Online (Sandbox Code Playgroud)

产量:

0
1
2
3
4
Run Code Online (Sandbox Code Playgroud)


Ale*_*her 5

如果你正在做shell命令而且你(像我一样)对流水线有一种迷恋,那么这个很好:

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


the*_*der 5

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

使用 seq

概要来自 man seq

$ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last
Run Code Online (Sandbox Code Playgroud)

句法

完整命令
seq first incr last

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

例子:

$ seq 1 2 10
1 3 5 7 9
Run Code Online (Sandbox Code Playgroud)

只有第一个和最后一个:

$ seq 1 5
1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

只有最后:

$ seq 5
1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

使用 {first..last..incr}

这里 first 和 last 是强制性的, incr 是可选的

只使用 first 和 last

$ echo {1..5}
1 2 3 4 5
Run Code Online (Sandbox Code Playgroud)

使用增量

$ echo {1..10..2}
1 3 5 7 9
Run Code Online (Sandbox Code Playgroud)

您甚至可以将其用于以下字符

$ 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
Run Code Online (Sandbox Code Playgroud)