【问题标题】:Running the output of a bash shell script as a shell command?将 bash shell 脚本的输出作为 shell 命令运行?
【发布时间】:2021-09-08 14:21:49
【问题描述】:

我有一个名为“test.sh”的 shell 脚本:

#!/usr/bin/env bash

echo "echo 'hello';"

当我运行 ./test.sh 时,它显然会给出以下输出:

回声“你好”;

现在这个输出本身也是一个有效的 shell 语句。如果我将此行复制并粘贴到外壳上并按回车键,它会说

你好

到目前为止一切顺利。
但是我想一步到位,所以我尝试了这个:

$(./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 命令行执行的正确语法是什么?

【问题讨论】:

    标签: bash shell terminal command execution


    【解决方案1】:

    问题是命令替换($( ) 的事情)是在解析命令行的过程中完成的——在它被解析并应用引号、分号和一堆其他东西之后。结果,当这些引号、分号等成为命令行的一部分时,它们做任何有用的事情都为时已晚,它们只是没有特殊含义的普通字符。

    这是您真正想要使用eval 的少数情况之一:

    eval "$(./test.sh)"
    

    eval 所做的是对其参数重新运行整个解析过程,因此它们中的任何 shell 语法(引号、分号等)都会得到完全解析。双引号是为了防止在命令替换之后但在结果传递给eval之前发生的一点点解析,这可能会导致奇怪的效果。

    顺便说一句,eval 的名声普遍很差,因为它可能会导致您不希望被视为 shell 语法的内容(如文件名等)被视为 shell 语法。但在这种情况下,这似乎正是您想要和期望的,所以就是这样。

    【讨论】:

    • 谢谢!非常适合我的情况。是的,我明白使用eval 时会有很多危险和陷阱,好点。还有一个问题:是否也可以同时显示 test.sh 的输出并使用单个命令行来执行它?
    • @RocketNuts 也打印它可能有点棘手;根据您的操作系统,您可能可以执行eval "$(./test.sh | tee /dev/stderr)" 之类的操作(正如您所料,它将副本发送到标准错误而不是标准输出——但它们通常都是终端)。另一种可能是cmd=$(./test.sh); echo "$cmd"; eval "$cmd"
    【解决方案2】:

    你有一个额外的echo。这是代码(test.sh):

    #!/bin/bash
    echo "hello"
    

    然后像这样运行它:$(./test.sh)"$(./test.sh)"。它将启动hello 命令。如果您希望test.sh 运行脚本,请在test.sh 中使用echo "./hello"(与./)。

    要在执行前显示命令,请使用bash -x test.sh 运行它

    【讨论】:

    • 第一个回显是让脚本输出一些东西。第二个回声在输出中,即我要显示的文本,它本身也是一个有效的命令。也许它们都是“回声”令人困惑,我将编辑我的问题并在其中添加其他内容。