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

时间:2015-02-20 11:29:45

标签: shell posix

考虑这个小shell脚本。

# Save the first command line argument
cmd="$1"

# 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="$1"

# 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

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

我并不反对set -o noglob。我只想知道是否还有其他办法。你知道,通过引用它们可以禁用通用命令行参数的globbing,所以我想知道是否有任何类似的命令替换。

1 个答案:

答案 0 :(得分:3)

如果我理解正确,您希望用户提供 shell命令作为命令行参数,该参数将由脚本执行,并且应该生成一个SQL字符串,处理(大写)并回显到标准输出。

首先要说的是,让用户提供一个脚本只是盲目执行的shell命令是没有意义的。如果脚本在执行之前对命令进行了某种修改/预处理,那么它可能有意义,但如果没有,那么用户也可以自己执行命令并将输出作为命令行传递给脚本论证,或通过stdin。

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

out=$(eval "$cmd");

需要对shell语法和扩展规则有相当深入的了解才能完全理解使用上述语法的基本原理,但基本上执行$cmd和执行eval "$cmd"会产生微妙的差异,从而使{ {1}}表单不适合执行给定的shell命令字符串。

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

通过$cmd运行命令,正确地双引号,确保完整的shell解析/处理/执行算法将应用于脚本用户提供的shell命令字符串。 / p>

第二个要说的是:如果您在脚本中尝试上述正确的表单,您会发现解决了您的问题。您仍然会获得eval作为输出。

原因是:由于您已决定要接受来自脚本用户的任意shell命令,现在用户负责正确引用可能嵌入在该段代码中的所有元字符。接受shell命令作为命令行参数是没有意义的,但是以某种方式更改了shell命令的处理规则,以便在执行给定的shell命令时某些元字符将不再是元字符。实际上,你可以做类似的事情,可能正如你发现的那样使用SELECT FILEA FILEB FILEC FROM TABLE,但那时必须成为脚本和脚本用户之间的契约;必须让用户知道执行命令时确切的处理规则是什么,以便他可以正确使用该脚本。

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

set -o noglob

我想回到我之前关于整体设计的评论;这样做是没有意义的。采用文本到进程本身更有意义,是一个预期会产生文本到进程的shell命令。

以下是如何做到的:

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

(我还删除了## take the text-to-process via a command-line argument sql="$1"; ## process and echo it echo "$sql"| tr a-z A-Z; 的{​​{1}}选项,这在这里真的没有意义。)

请注意,脚本现在更简单,使用也更简单:

-s