问题描述
#!/usr/bin/env bash
echo "echo 'hello';"
当我运行 ./test.sh
时,它显然给出了以下输出:
echo '你好';
现在这个输出也是一个有效的 shell 语句本身。如果我将这一行复制并粘贴到 shell 上并按 Enter,它会说
你好
到目前为止一切顺利。
但是我想一步到位,所以我尝试了这个:
$(./test.sh)
但是我现在得到:
'你好';
以防万一,我使用的是 macOS 10.15.6 Catalina,它默认使用 zsh
shell。我在 Linux 上使用 bash 尝试了同样的方法,结果相同。
为了以防万一,我也尝试了 $('./test.sh')
或 $("./test.sh")
,但显然没有任何区别。
对此可能有一些简单的解释和解决方案,但我看不到。我做错了什么?
(编辑)也许这两个回声令人困惑,假设我的 test.sh 脚本包含这个:
echo "ls *.txt;"
如果我现在做./test.sh
,我会得到
ls *.txt;
如果我将其复制并粘贴到 shell 上,它会按预期显示所有 .txt 文件。
但是,如果我执行 $(./test.sh)
,我会得到:
ls: *.txt;: 没有那个文件或目录
让 test.sh 脚本输出的任何内容作为 shell 命令行执行的正确语法是什么?
解决方法
问题是命令替换($( )
事情)是在解析命令行的过程中完成的——在解析并应用引号、分号和其他一些东西之后。结果,当这些引号、分号等成为命令行的一部分时,它们做任何有用的事情都为时已晚,它们只是没有特殊含义的普通字符。
这是您真正想要使用 eval
的少数情况之一:
eval "$(./test.sh)"
eval
所做的是对其参数重新运行整个解析过程,因此它们中的任何 shell 语法(引号、分号等)都会被完全解析。双引号是为了防止在命令替换之后但在结果传递给 eval
之前发生的一点点解析,这可能会导致奇怪的效果。
顺便说一句,eval
的名声通常很差,因为它可能导致您不希望被视为 shell 语法的东西(如文件名等)被视为 shell 语法。但在这种情况下,这似乎正是您想要和期望的,所以就是这样。
您有一个额外的 echo
。这是代码(test.sh):
#!/bin/bash
echo "hello"
然后像这样运行它:$(./test.sh)
或 "$(./test.sh)"
。它将启动 hello
命令。如果您希望 test.sh
运行脚本,请在 echo "./hello"
中使用 test.sh
(使用 ./
)。
要在执行前显示命令,请使用 bash -x test.sh