当范围由变量给定时,如何在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}
当前回答
这就是为什么最初的表达不起作用。
来自man bash:
在之前进行支撑扩展任何其他扩展,以及其他特殊字符扩展保留在后果它是严格的文本。猛击不应用任何语法对上下文的解释支撑。
因此,大括号扩展是在参数扩展之前作为纯文本宏操作完成的。
外壳是宏处理器和更正式的编程语言之间高度优化的混合体。为了优化典型用例,语言变得更加复杂,并且接受了一些限制。
正式建议
我建议坚持使用Posix1功能。这意味着在<list>中使用for i;如果列表已经知道,则使用while或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。脚本可能需要更快的外壳、更安全的外壳和更嵌入式的外壳。他们可能需要在任何安装为/bin/sh的设备上运行,然后就有了所有常见的支持标准的论点。还记得shellshock,又名bashtoor吗?
其他回答
这适用于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"
}
这在bash中运行良好:
END=5
i=1 ; while [[ $i -le $END ]] ; do
echo $i
((i = i + 1))
done
这是另一种方式:
end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
如果希望尽可能接近大括号表达式语法,请尝试使用bash-tricks的range.bash中的range函数。
例如,以下所有操作将执行与echo{1..10}完全相同的操作:
source range.bash
one=1
ten=10
range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}
它试图用尽可能少的“gotchas”来支持本机bash语法:不仅支持变量,而且还防止了作为字符串提供的无效范围(例如,对于{1..a}中的i;do echo$i;done)的通常不希望的行为。
其他答案在大多数情况下都有效,但它们都至少有以下缺点之一:
其中许多都使用子外壳,这可能会损害性能,在某些系统上可能不可能。他们中的许多人依赖外部程序。甚至seq也是一个二进制文件,必须安装才能使用,必须由bash加载,并且必须包含您期望的程序,才能在这种情况下工作。无论是否无处不在,这比Bash语言本身更值得依赖。只使用本机Bash功能的解决方案,如@ephemient,将无法在字母范围内工作,如{a..z};支架膨胀将。不过,问题是关于数字的范围,所以这是一个狡辩。它们中的大多数在视觉上与{1..10}大括号扩展范围语法并不相似,因此使用这两种语法的程序可能有点难以阅读。@bobobogo的答案使用了一些熟悉的语法,但如果$END变量不是范围另一侧的有效范围“bookend”,则会出现一些意外情况。例如,如果END=a,则不会发生错误,并且将回显逐字值{1..a}。这也是Bash的默认行为——这通常是意外的。
免责声明:我是链接代码的作者。
讨论
正如加亚罗所建议的那样,使用seq是可以的。Pax Diablo建议使用Bash循环来避免调用子进程,如果$END太大,则具有更友好内存的额外优势。Zathur发现了循环实现中的一个典型错误,并暗示,由于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允许((expr;expr;expr))构造。由于我从未阅读过Bash的整个手册页(就像我很久以前在Kornshell(ksh)手册页上所做的那样),所以我错过了这一点。
So,
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()。