我对命令替换感到困惑。我认为命令替换就像编程语言宏。在评估父命令之前,首先执行子 shell,并$(...)
用其标准输出替换 。但这就是全部真相吗?
当我尝试执行以下命令时
echo {0..9} | xargs -n 2 $(echo 'echo | tac')
我希望看到以下输出
8 9
6 7
4 5
2 3
0 1
但相反得到这个
| tac 0 1
| tac 2 3
| tac 4 5
| tac 6 7
| tac 8 9
那么是什么原因导致| tac
被捕获为 echo 的参数,而不是作为命令行的一部分呢?确切的机制和顺序是什么,如何解析、确定范围和评估命令行和命令替换?是否有可能以元编程方式在 Linux 中动态构建命令行?
编辑
我知道我可以使用echo {0..9}| xargs -n 2 | tac
.这个问题是理论上的,我感兴趣为什么子 shell 示例没有产生相同的结果
答案1
您所看到的问题的根源是由于命令行的标记化发生方式所致。在这种特殊情况下,早在$(echo 'echo | tac')
展开之前,shell 就已经知道您打算运行一个命令 ( echo {…}
) 并通过管道 ( |
) 将其输出传递给另一个命令 ( xargs -n 2 $(…)
)。
然后在下一阶段,填充$(…)
和大括号扩展{…}
是否会生成要运行的实际命令。在这个阶段,如果它产生了管道字符,那又怎么样,已经太晚了,而且显然错过了公共汽车。
现在,它将不被视为特殊元字符,而是将xargs
作为任何普通(=>非元字符)字符包含在命令行中。
如果你想再试一次,那么你就需要这样做eval
。
eval "echo {0..9} | xargs -n 2 $(echo 'echo | tac')"
这将输出您期望的内容。
答案2
除了罗马所说,你的命令应该是这样的
echo {0..9}| xargs -n 2 | tac
这会产生
8 9
6 7
4 5
2 3
0 1
使用毫无意义的 $(echo 'echo') 这会导致错误
附言。对于长答案,|
将输出从一个命令重定向到下一个命令,因此echo {0..9}
生成:
0 1 2 3 4 5 6 7 8 9
xargs -n 2
接受该输入并产生
0 1
2 3
4 5
6 7
8 9
最后tac
获取该输入并生成相反的表达式:
8 9
6 7
4 5
2 3
0 1
我希望这更清楚一些。
编辑:
echo {0..9} | xargs -n 2 $(echo 'echo | tac')
+ echo 0 1 2 3 4 5 6 7 8 9
++ echo 'echo | tac'
+ xargs -n 2 echo '|' tac
| tac 0 1
| tac 2 3
| tac 4 5
| tac 6 7
| tac 8 9
这是您使用初始语法发送到 bash 的命令,很清楚发生了什么,最后您发送xargs -n 2 echo '|' tac
答案3
冒着多余的风险(但我没有看到其他人在哪里明确说过这一点),你的原因
echo {0..9} | xargs -n 2 $(echo 'echo | tac')
命令的作用是它相当于
echo {0..9} | xargs -n 2 'echo' '|' 'tac'
相比于
echo {0..9} | xargs -n 2 echo foo bar
发生这种情况是因为 是在解析$(echo …)
命令行 ( ) 的整体结构之后进行评估的,因此 的输出只能向命令添加参数。要以您想要的方式做您想做的事,您需要说echo (something) | xargs (args)
$(echo …)
xargs
回声{0..9} |评估xargs -n 2 $(echo 'echo | tac')
请注意,这eval
可能很危险。搜索此网站以获取有关此内容的警告,并注意它们。