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


11

从以下内容开始: Shell命令替换中的意外行为

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

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

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

我试图通过简单地简化

./othercommand $(./somecommand)

并遇到了上面提到的意外行为。问题是-可以可靠地使用命令替换生成参数,othercommand前提是某些参数需要引用并且不能更改?


取决于您“可靠”的意思。如果您希望下一个命令完全按照屏幕上显示的方式输出输出,并对其应用外壳规则,则eval可以使用它,但是通常不建议这样做。xargs也是要考虑的事情
Sergiy Kolodyazhnyy

我想(期望)输出somecommand进行常规的shell解析
user1207217

就像我在回答中说的那样,请使用其他一些字符进行字段拆分(例如:)...假设该字符将可靠地不在输出中。
Olorin

但这并不完全正确,因为它不遵守报价规则,这就是问题所在
user1207217

2
您能发表一个真实的例子吗?我的意思是,第一个命令的实际输出以及您希望它如何与第二个命令交互。
nxnev

Answers:


10

我写了一个脚本,可以为我生成引号

如果输出正确地引用了外壳,并且您信任该输出,则可以eval在其上运行。

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

如果./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[@]}"未更改的引数扩展为所有元素。不带引号的数组元素将进行单词拆分。请参阅例如BashGuideArrays页面。)

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


如果不使用eval很难解析shell引用,则可以在脚本输出中使用其他一些字符作为分隔符。您需要选择实际参数中不需要的一个。

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

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

IFS如果您依赖脚本中其他位置的分词,则需要稍后再进行设置(unset IFS应该将其设置为默认值),并且set +f如果您以后要使用遍历功能,也需要使用。

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


两全其美!
奥洛林

您是否要添加一句话以表明引用的重要性${args[@]}-否则这对我不起作用
user1207217

@ user1207217,是的,您是对的。数组和"${array[@]}"都是一样的"$@"。两者都需要加引号,否则单词拆分会将数组元素分成多个部分。
ilkkachu

6

问题是,一旦somecommand脚本输出的选项othercommand,这些选项实际上只是文本,并且受外壳程序标准解析的影响(受$IFS发生的一切以及有效的外壳程序选项的影响,通常情况下您不会这样做)控制)。

而不是使用somecommand输出的选项,它会更容易,更安全,更稳健的用它来打电话 othercommand。该somecommand脚本将成为包装脚本othercommand而不是您必须记住以某种特殊方式调用的辅助脚本,作为该脚本的一部分otherscript。包装脚本是提供工具的一种非常常见的方式,该工具仅使用另一组选项调用其他相似的工具(只需检查其中的file命令/usr/bin实际上是Shell脚本包装器即可)。

bashksh或者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

在中/bin/sh,用于$@按住选项:

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

othercommand "$@"

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

请注意,它是双引号,$@${options[@]}确保了这些元素不会被单独单词拆分(和文件名已遍历)。


你能解释一下set --吗?
user1207217

@ user1207217添加了解释说明。
Kusalananda

4

如果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,否则不可能可靠地使用其输出来构建命令字符串。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.