如何通过命令替换生成另一个命令的参数

如何通过命令替换生成另一个命令的参数

继自: shell 命令替换中的意外行为

我有一个命令可以接受大量参数,其中一些可以合法地包含空格(可能还有其他东西)

我编写了一个脚本,可以为我生成这些参数,并带有引号,但我必须复制并粘贴输出,例如

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>

我试图通过简单地简化这个

./othercommand $(./somecommand)

并遇到了上面提到的意外行为。问题是——othercommand鉴于某些参数需要引用并且无法更改,命令替换是否可以可靠地用于生成参数?

答案1

我编写了一个脚本,可以为我生成这些参数,并带有引号

如果 shell 的输出被正确引用,并且您信任输出,然后你就可以运行eval它了。

假设您有一个支持数组的 shell,那么最好使用一个 shell 来存储您获得的参数。

如果./gen_args.sh产生类似的输出'foo bar' '*' asdf,那么我们可以运行来填充一个用结果调用eval "args=( $(./gen_args.sh) )"的数组。这args就是三个元素foo bar,,,*asdf

我们可以"${args[@]}"像平常一样单独扩展数组元素:

$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

(注意引号。"${array[@]}"扩展为所有元素作为未修改的不同参数。没有引号,数组元素将受到分词的影响。请参见例如BashGuide 上的数组页面.)

然而eval将愉快地运行任何 shell 替换,因此$HOME输出将扩展到您的主目录,并且命令替换实际上会在运行的 shell 中运行命令eval。的输出"$(date >&2)"将创建一个空数组元素并在标准输出上打印当前日期。如果gen_args.sh从某些不受信任的来源(例如网络上的另一台主机、其他用户创建的文件名)获取数据,则这是一个问题。输出可以包含任意命令。(如果get_args.sh本身是恶意的,则不需要输出任何内容,直接运行恶意命令即可。)


shell 引用的替代方法是在脚本的输出中使用其他字符作为分隔符,如果没有 eval 就很难解析 shell 引用。您需要选择实际参数中不需要的一个。

让我们选择#,并让脚本输出foo bar#*#asdf。现在我们可以使用未引用的命令扩展将命令的输出拆分为参数。

$ IFS='#'                          # split on '#' signs
$ set -f                           # disable globbing
$ args=( $( ./gen_args3.sh ) )     # assign the values to the array
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:

IFS如果您在脚本中的其他地方依赖于分词(unset IFS应该可以使其成为默认值),则需要稍后进行设置,并且set +f如果您想稍后使用通配符,也可以使用。

如果您不使用 Bash 或其他具有数组的 shell,则可以使用位置参数。替换args=( $(...) )set -- $(./gen_args.sh)并使用then"$@"代替"${args[@]}"。 (在这里,您也需要在 周围加上引号"$@",否则位置参数会受到分词的影响。)

答案2

问题是,一旦您的somecommand脚本输出 的选项othercommand,这些选项实际上只是文本,并且受 shell 标准解析的支配(受到$IFS发生的情况以及有效的 shell 选项的影响,在一般情况下您会不是能够控制)。

而不是somecommand使用输出选项,使用它会更容易、更安全、更强大称呼 othercommand。该somecommand脚本将是包装脚本aroundothercommand而不是某种帮助程序脚本,您必须记住以某种特殊方式作为otherscript.包装器脚本是一种非常常见的提供工具的方法,该工具仅使用另一组选项调用其他一些类似的工具(只需检查file其中的命令/usr/bin实际上是 shell 脚本包装器)。

bash,ksh或中zsh,您可以轻松地创建一个包装器脚本,该脚本使用数组来保存各个选项,如下othercommand所示:

options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" )  # add additional option

然后调用othercommand(仍在包装器脚本中):

othercommand "${options[@]}"

的扩展"${options[@]}"将确保数组的每个元素都被单独引用并作为单独的参数options呈现。othercommand

包装器的用户会忘记它实际上正在调用的事实othercommand,这会不是如果脚本只是生成命令行选项作为othercommand输出,则为 true。

在 中/bin/sh,用于$@保存选项:

set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!"  # add additional option

othercommand "$@"

(set是用于设置位置参数的命令$1,等$2。这些是在标准 POSIX shell 中$3组成数组的内容。首字母表示没有给出选项,只有参数。是$@--set--真的仅当第一个值恰好是以-) 开头时才需要。

请注意,它是双引号$@${options[@]}确保元素不会单独进行单词分割(以及文件名通配)。

答案3

如果somecommand输出采用可靠且良好的 shell 语法,您可以使用eval

$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello 
hi and bye

但您必须确保输出具有有效的引用等,否则您最终可能还会在脚本外部运行命令:

$ cat test.sh 
for var in "$@"
do
    echo "|$var|"
done
$ ls
bar  baz  test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh

请注意,它echo rm bar baz test.sh没有传递给脚本(因为;)并且作为单独的命令运行。我添加了|周围$var来突出这一点。


通常,除非您可以完全信任 的输出somecommand,否则不可能可靠地使用其输出来构建命令字符串。

相关内容