shell 脚本中的命令替换没有通配

Command substitution in shell script without globbing

考虑这个 shell 脚本。

# Save the first command line argument
cmd=""

# Execute the command specified in the first command line argument
out=$($cmd)

# Do something with the output of the specified command
# Here we do a silly thing, like make the output all uppercase
echo "$out" | tr -s "a-z" "A-Z"

脚本执行指定为第一个参数的命令,转换从该命令获得的输出并将其打印到标准输出。该脚本可能以这种方式执行。

sh foo.sh "echo select * from table"

这不是我想要的。它可能打印如下内容,

$ sh foo.sh "echo select * from table"
SELECT FILEA FILEB FILEC FROM TABLE

如果 fileA、fileB 和 fileC 存在于当前目录中。

从用户的角度来看,这个命令是合理的。用户在命令行参数中引用了 *,因此用户不希望 * 被通配。但是我的脚本通过在命令替换中使用此参数使用户感到惊讶,这导致 * 如上面的输出所示。

我希望输出如下。

SELECT * FROM TABLE

cmd 中的整个文本实际上来自脚本的命令行参数,因此我想保留参数中出现的任何 * 符号而不用通配符。

我正在寻找适用于任何 POSIX shell.

的解决方案

我想出的一个解决方案是在命令替换之前用 set -o noglob 禁用 globbing。这是完整的代码。

# Save the first command line argument
cmd=""

# Execute the command specified in the first command line argument
set -o noglob
out=$($cmd)

# Do something with the output of the specified command
# Here we do a silly thing, like make the output all uppercase
echo "$out" | tr -s "a-z" "A-Z"

这符合我的预期。

$ sh foo.sh "echo select * from table"
SELECT * FROM TABLE

除此之外,是否有任何其他概念或技巧(例如引用机制)我需要注意仅在命令替换中禁用 globbing 而不必使用 set -o noglob.

我不反对set -o noglob。我只是想知道是否有另一种方法。你知道,可以通过引用它们来禁用普通命令行参数的 globbing,所以我想知道是否有类似的命令替换。

如果我没理解错的话,你希望用户提供一个shell命令作为命令行参数,它会被脚本执行,并且是预计会产生一个 SQL 字符串,该字符串将被处理(大写)并回显到标准输出。

首先要说的是,让用户提供脚本盲目执行的 shell 命令是没有意义的。如果脚本在执行命令之前应用了某种 modification/preprocessing 命令,那么它可能有意义,但如果没有,那么用户不妨自己执行命令并将输出作为命令传递给脚本-line 参数,或通过 stdin.

不过话虽这么说,如果你真的想这样做,那么有两件事需要说。首先,这是正确的使用形式:

out=$(eval "$cmd");

需要对 shell 语法和扩展规则有相当深入的了解才能完全理解使用上述语法的基本原理,但基本上执行 $cmd 和执行 eval "$cmd" 有细微的差别使得 $cmd 形式不适合执行给定的 shell 命令字符串。

只是提供一些细节,希望能澄清上述观点,扩展有七种shell在处理时按以下顺序执行输入:(1) 大括号扩展,(2) 波浪号扩展,(3) 参数和变量扩展,(4) 算术扩展,(5) 命令替换,(6) 分词,以及 (7) 路径名扩展。请注意,变量扩展 发生在该序列的中间,因此变量扩展的 shell 命令(由用户提供)将不会受益于先前的扩展类型。其他问题是前导变量赋值、管道和命令列表令牌在 $cmd 形式下将无法正确执行,因为它们在变量扩展之前被解析和处理(实际上在 all 扩展)也是如此。

通过 运行 通过 eval 的命令,正确地用双引号引起来,确保完整的 shell parsing/processing/execution 算法将应用于 shell 脚本用户给出的命令字符串。

第二个要说的是:如果你在你的脚本中尝试上面的正确形式,你会发现它没有解决你的问题。您仍然会得到 SELECT FILEA FILEB FILEC FROM TABLE 作为输出。

原因是这样的:既然你已经决定要接受来自脚本用户的任意 shell 命令,现在是 用户的 责任正确引用可能嵌入该代码段的所有元字符。将 shell 命令作为命令行参数接受是没有意义的,但以某种方式更改 shell 命令的处理规则,以便某些元字符在给定 shell 命令被执行。实际上,您 可以 做类似的事情,也许正如您发现的那样使用 set -o noglob,但是那必须成为脚本和脚本用户之间的契约;必须让用户知道命令执行时具体的处理规则是什么,才能正确使用脚本。

在这种设计下,用户可以按如下方式调用脚本(注意 shell 命令字符串评估的额外引号层;也可以反斜杠转义星号):

$ sh foo.sh "echo 'select * from table'";

我想 return 回复我之前对整体设计的评论;这样做真的没有意义。采取文本处理本身更有意义,不是一个预期产生文本处理的shell命令。

这是如何做到的:

## take the text-to-process via a command-line argument
sql="";

## process and echo it
echo "$sql"| tr a-z A-Z;

(我还去掉了tr-s选项,这里真的没有意义。)

注意现在的脚本更简单了,用法也更简单了:

$ sh foo.sh 'select * from table';