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

use*_*217 11 shell command-substitution arguments

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

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

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

./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>
Run Code Online (Sandbox Code Playgroud)

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

./othercommand $(./somecommand)
Run Code Online (Sandbox Code Playgroud)

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

ilk*_*chu 10

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

如果 shell 正确引用了输出,并且您信任 output,那么您可以eval在其上运行。

假设你有一个支持数组的 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:
Run Code Online (Sandbox Code Playgroud)

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

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


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

让我们选择#,并让脚本输出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:
Run Code Online (Sandbox Code Playgroud)

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

如果您不使用 Bash 或其他具有数组的 shell,则可以为此使用位置参数。替换args=( $(...) )set -- $(./gen_args.sh)并使用"$@"而不是"${args[@]}"then。(在这里,您也需要将 引号括起来"$@",否则位置参数会被分词。)


Kus*_*nda 6

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

与其使用somecommand输出选项,不如使用它来调用 othercommand. somecommand然后,该脚本将是一个包装脚本othercommand而不是某种帮助程序脚本,您必须记住以某种特殊方式将其作为otherscript. 包装脚本是提供工具的一种非常常见的方式,该工具仅调用具有另一组选项的其他类似工具(只需检查中的file哪些命令/usr/bin实际上是 shell 脚本包装)。

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

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

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

othercommand "${options[@]}"
Run Code Online (Sandbox Code Playgroud)

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

包装器的用户会忽略它实际上正在调用 的事实,如果脚本只是生成命令行选项作为输出othercommand,那么这将是正确的othercommand

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

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

othercommand "$@"
Run Code Online (Sandbox Code Playgroud)

set是用于设置位置参数的命令$1$2$3等等,这些都是由什么组成的阵列$@的初始在一个标准的POSIX外壳。--是通知set不存在任何给定的选项,仅参数。该--确实仅需要当第一个值恰好是以-)开头的值。

请注意,这是周围的双引号$@,并${options[@]}认为确保元素不是个别字分裂(和文件名匹配替换)。