当范围由变量给出时,如何迭代 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}
当范围由变量给出时,如何迭代 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}
for i in $(seq 1 $END); do echo $i; done
编辑:我更喜欢seq
其他方法,因为我实际上可以记住它;)
该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 将它们视为算术。
正如贾罗建议的那样,使用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
和都是echo
Bash 内置函数,但fork()
命令替换($(…)
构造)需要 a。
这就是为什么原始表达式不起作用的原因。
从man bash:
大括号扩展在任何其他扩展之前执行,并且任何其他扩展的特殊字符都保留在结果中。它是严格的文本。Bash 不对扩展的上下文或大括号之间的文本应用任何句法解释。
因此,大括号扩展是在参数扩展之前作为纯文本宏操作尽早完成的。
Shell 是宏处理器和更正式的编程语言之间高度优化的混合体。为了优化典型用例,语言变得相当复杂,并且接受了一些限制。
推荐
我建议坚持使用 Posix 1功能。这意味着使用for i in <list>; do
,如果列表已知,否则使用while
or 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
POSIX 方式
如果您关心可移植性,请使用POSIX 标准中的示例:
i=2
end=5
while [ $i -le $end ]; do
echo $i
i=$(($i+1))
done
输出:
2
3
4
5
不是POSIX的东西:
(( ))
没有美元,尽管它是POSIX 本身提到的常见扩展。[[
. [
这里就够了。另请参阅:Bash 中的单方括号和双方括号有什么区别?for ((;;))
seq
(GNU Coreutils){start..end}
,并且不能与Bash 手册中提到的变量一起使用。let i=i+1
: POSIX 7 2. Shell 命令语言不包含单词let
, 并且在bash --posix
4.3.42上失败可能需要美元i=$i+1
,但我不确定。POSIX 7 2.6.4 算术扩展说:
如果 shell 变量 x 包含一个形成有效整数常量的值,可选地包括前导加号或减号,那么算术扩展 "$((x))" 和 "$(($x))" 将返回相同的值价值。
但从字面上阅读它并不意味着$((x+1))
扩展,因为x+1
它不是一个变量。
另一层间接:
for i in $(eval echo {1..$END}); do
∶
您可以使用
for i in $(seq $END); do echo $i; done
如果你需要它的前缀,那么你可能会喜欢这个
for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done
这将产生
07
08
09
10
11
12
我在这里结合了一些想法并测量了性能。
seq
而且{..}
真的很快for
并且while
循环很慢$( )
是缓慢的for (( ; ; ))
循环较慢$(( ))
甚至更慢这些不是结论。您必须查看每个背后的 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
如果你在 BSD / OS X 上,你可以使用 jot 而不是 seq:
for i in $(jot $END); do echo $i; done
这适用于bash
:
END=5
i=1 ; while [[ $i -le $END ]] ; do
echo $i
((i = i + 1))
done
我知道这个问题是关于 的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}
这是另一种方式:
end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
如果您想尽可能接近大括号表达式语法,请尝试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}
它试图用尽可能少的“陷阱”来支持本机 bash 语法:不仅支持变量,而且for i in {1..a}; do echo $i; done
还防止了作为字符串(例如)提供的无效范围的常见不良行为。
其他答案在大多数情况下都有效,但它们都至少具有以下缺点之一:
seq
是一个必须安装才能使用的二进制文件,必须由 bash 加载,并且必须包含您期望的程序,才能在这种情况下工作。不管是否无处不在,这不仅仅依赖于 Bash 语言本身。{a..z}
; 大括号展开会。不过,问题是关于数字范围的,所以这是一个小问题。{1..10}
大括号扩展范围语法并不相似,因此使用两者的程序可能会有点难以阅读。$END
变量不是范围另一侧的有效范围“书挡”,则会出现意想不到的情况。例如,如果END=a
不会发生错误,则会{1..a}
回显逐字记录值。这也是 Bash 的默认行为——只是经常出乎意料。免责声明:我是链接代码的作者。
有很多方法可以做到这一点,但我更喜欢的方法如下
seq
概要来自
man seq
$ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last
句法
完整的命令
seq first incr 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
这些都很好,但 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 的第二个实例,因此它应该适合密集型操作。
替换{}
为(( ))
:
tmpstart=0;
tmpend=4;
for (( i=$tmpstart; i<=$tmpend; i++ )) ; do
echo $i ;
done
产量:
0
1
2
3
4
如果您正在执行 shell 命令,并且您(像我一样)喜欢流水线,那么这个很好:
seq 1 $END | xargs -I {} echo {}
如果您不想使用 ' 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
seq
PS:我的 bash无论如何都没有 ' ' 命令。
在 Mac OSX 10.6.8、Bash 3.2.48 上测试
这在 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"
}