人们可以将算术扩展和大括号扩展结合起来吗?
$ for i in {$((1 + 1))..5}; do echo $i; done;
{2..5}
$ echo "bash laughs at me"
答案1
man bash
解释见:
扩展的顺序是:大括号扩展、波形符扩展、参数、变量和算术扩展以及命令替换(以从左到右的方式完成)、分词和路径名扩展。
大括号扩展发生在算术扩展之前,因此您无法按照您尝试的方式组合它们。
改用seq
:
for i in $(seq $((a+4)) 12) ; do echo $i ; done
答案2
eval 'for i in {'"$((1 + 1))"'..5}; do echo $i; done'
输出
2
3
4
5
一般来说,如果您希望 shell 在实际执行某些操作之前执行某些操作,则必须让 shell 再检查一下。这是eval
的功能 - 它两次评估一个命令。 shell 解析其对 shell 扩展的求值——这通常不会发生。
考虑一下:
v='"quotes in here"'; printf %s\\n $v
"quotes
in
here"
您会看到引号由 shell 的解析器解释,该解析器处理输入前进行了扩展,因此其中的引号$v
没有任何意义 - 没有解析器来理解它们。但如果我这样做:
v='"quotes in here"'; eval "printf '%s\\n' $v"
quotes in here
输出突然不同了。区别在于解析器 - 它决定其输入的哪些位是命令以及为什么。这是与;${}()||''""&& while for if
所有此类东西一起使用的部分。这并不意味着{}
后面的大括号与您所询问的大括号相同 - 正如其他答案已经显示的那样,这些大括号显然是作为bash
扩展单独处理的。无论如何,通常您需要某种形式的第二次评估来处理这个问题。
正是这一点才造成了eval
危险。当您"${expansion}"
引用扩展时,您将其标记为解析器 - 您划界它。 shell 知道,无论发生什么,这都只是命令中的一项(可能是一个参数)。即使你不这样做; shell 不会接受$expansion
超出;
简单命令边界的命令 - 因为该简单命令也已经是分隔的。但是 - 就像它所做的那样eval
- 如果 shell 返回并再次查看该扩展,它可能会找到可以运行的命令 -甚至几个- 这就是eval
工作原理。
因此,在使用时eval
必须非常小心,不要意外地两次评估 shell 令牌或任何可能包含一个的扩展- 并且您只在第一次循环时评估外壳不能足够快地完成的部分,否则。最佳实践是仅在不同时间单独评估命令的任何一个部分,如下所示。
这eval
又是第一个字符串:
eval \ #inital command
'for i in {'\ #hard-quoted - not expanded or executed
"$((1 + 1))"\ #double-quoted - expanded and delimited
'..5}; do echo $i; done' #hard-quoted - not expanded or executed
上面的评论讨论了在第一轮中采取的行动eval
- 还有一个要进行。在这种情况下,您想要1+1
.所以我们首先扩展它,而不是其他。我们能摆脱的最坏情况是2
。这不是 shell 解析器标记,可以安全地求值 - 甚至可以用双引号引起来。事实上,如果eval
说它有任何真正有效的用途的话,那就是算术。其他所有内容都是硬引用的 - 不采取任何操作,只是连接字符串并删除引号,就像任何其他硬引用字符串一样。
但是当它第二次返回时——在第一次传递中删除引号之后——shell 发现:
for i in {2..5}; do echo $i; done
它确实做到了。
还有其他方法可以解决这个问题:
bash -c "for i in {$((1+1))..5}; do echo \$i; done"
^有效。
和:
. /dev/fd/0 <<CMD
for i in {$((1+1))..5}; do echo \$i; done
CMD
^有效。在每种情况下,逻辑都是相同的 - 在评估大括号扩展之前评估变量bash
。
不过,我自己从来没有大量使用过支架扩展。我通常喜欢:
until [ $((i=$i+1)) -gt 5 ]
do printf %d\\n $i
done