shell脚本中的命令替换(不带globbing)

shell脚本中的命令替换(不带globbing),shell,posix,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 a

考虑一下这个小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
禁用全局绑定。这是完整的代码

# 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,因此我想知道命令替换是否有类似的功能。

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

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

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

out=$(eval "$cmd");
需要对shell语法和扩展规则有相当深入的理解,才能完全理解使用上述语法的基本原理,但基本上是执行
$cmd
和执行
eval“$cmd”
有细微的差异,使得
$cmd
表单不适合执行给定的shell命令字符串

仅给出希望澄清上述观点的一些细节,shell在处理输入时按以下顺序执行七种扩展:(1)大括号扩展,(2)平铺扩展,(3)参数和变量扩展,(4)算术扩展,(5)命令替换,(6)字拆分,和(7)路径名扩展。注意,变量扩展发生在该序列的中间,因此,变量展开的shell命令(由用户提供)将不接受先前扩展类型的好处。其他问题是在
$cmd
表单下无法正确执行前导变量赋值、管道和命令列表标记,因为它们也是在变量展开之前(实际上在所有展开之前)进行解析和处理的

通过正确地双引号运行
eval
命令,可以确保将完整的shell解析/处理/执行算法应用于脚本用户给出的shell命令字符串

第二件要说的是:如果你在你的脚本中尝试上述正确的形式,你会发现它并没有解决你的问题。您仍然可以从表中选择文件A文件B文件C作为输出

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

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

我想回到我之前对整体设计的评论;这样做真的没有意义。更合理的做法是让文本自行处理,而不是使用shell命令生成要处理的文本

以下是如何做到这一点:

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

## process and echo it
echo "$sql"| tr a-z A-Z;
(我还删除了
tr
-s
选项,这在这里真的没有意义。)

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

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

您的意思是希望输出为
select*from table
?您的问题目前表明,所需输出与实际输出相同(
从表中选择fileA fileB fileC)。@bgoldst您是对的。我
$ sh foo.sh 'select * from table';