【问题标题】:Pass shell-escaped string of arguments to a subcommand in Bourne shell将 shell 转义的参数字符串传递给 Bourne shell 中的子命令
【发布时间】:2012-04-16 11:25:30
【问题描述】:

假设我有一个要运行的命令 (cmd) 和一个包含我要传递给函数的参数的变量(类似于 --foo 'bar baz' qux)。像这样:

#!/bin/sh
command=cmd
args="--foo 'bar baz' qux"

参数包含引号,如上所示,将包含空格的参数组合在一起。然后我想运行命令:

$command $args

当然,这会导致运行带有 四个 参数的命令:--foo'barbaz'qux。我习惯的替代方案(即使用"$@" 时)提出了一个不同的问题:

$command "$args"

这将执行带有 one 参数的命令:--foo 'bar baz' qux

如何按预期运行带有 三个 参数(--foobar bazqux)的命令?

【问题讨论】:

  • 学究式一点:现代/bin/sh 是 POSIX sh,而不是 Bourne。 (...不,POSIX sh 不是 1970 年代 Bourne 的纯超集:例如,在 Bourne 中——但不是 POSIX sh 或现代派生词,如 ash、dash、ksh 或 bash -- ^ 是一个有效的竖线字符)。
  • 我看到了你的迂腐,让你又多了一点迂腐:Solaris 10 是“现代的”(在 21 世纪问世,仍在生产中使用,但幸运的是现在正在减少),然而它的 /bin/sh 绝对不符合 POSIX。

标签: shell escaping arguments quotes sh


【解决方案1】:

通过更改 shell 的 IFS(内部字段分隔符)(只包含换行符),它对我有用。以下是我如何覆盖使用引用参数的命令:

$ cat ~/.local/bin/make
#!/bin/sh

# THIS IS IMPORTANT! (don't split up quoted strings in arguments)
IFS="
"
exec /usr/bin/make ${@} -j6

(/bin/shdash)
将命令替换为echo 时会吃掉引号,因此在“测试”时会出现错误;但该命令确实按预期获取它们。

可以通过将exec行替换为

来测试
for arg in $@; do echo $arg; done

【讨论】:

    【解决方案2】:

    如果您可以丢弃当前的位置变量 ($1...),您可以使用以下内容:

    set -- '--foo' 'bar baz' 'qux'
    echo "$#" # Prints "3" (without quotes)
    echo "$2" # Prints "bar baz" (without quotes)
    command "$@"
    

    刚刚在#!/usr/bin/env sh 脚本中对其进行了测试,因此它至少可以在 Dash 中运行,并且应该可以在任何 Bourne Shell 变体中运行。不需要eval,需要 Python 或 Bash。

    【讨论】:

      【解决方案3】:

      如果您有以下形式的命令:

      args="--foo 'bar baz' qux"
      

      首先将命令作为数组获取不是一种选择,然后您需要使用eval 将其转回数组:

      $ args="--foo 'bar baz' qux"
      $ eval "arr=($args)"
      

      但请务必注意,如果$args 由不受信任的来源提供,这是不安全的,因为它可用于执行任意命令,例如args='$(rm -rf /)'; eval "arr=($args)" 将导致上述代码在您使用 arr 之前运行 rm -rf /

      然后您可以使用"${arr[@]}" 将其扩展为命令的参数:

      $ bash -c 'echo $0' "${arr[@]}"
      --foo
      $ bash -c 'echo $1' "${arr[@]}"
      bar baz
      

      或者运行你的命令:

      "$command" "${arr[@]}"
      

      请注意,${arr[*]}${arr[@]}"${arr[*]}""${arr[@]}" 之间存在差异,在大多数情况下,只有最后一个可以满足您的需求

      【讨论】:

      • 好建议,但 OP 正在询问 bourne shell。
      【解决方案4】:

      一种可能是使用eval:

      #!/bin/sh
      
      args="--foo 'bar baz' qux"
      cmd="python -c 'import sys; print sys.argv'"
      
      eval $cmd $args
      

      这样你就可以解释命令行而不是仅仅根据IFS 进行拆分。这给出了输出:

      $ ./args.sh 
      ['-c', '--foo', 'bar baz', 'qux']
      

      这样你就可以看到参数是按照你的意愿传递的。

      【讨论】:

      • 不需要 Python:在 bash 中,printf %q "$@" 将以明确的形式显示您的 argv。 (对于所有阅读者——请仅在您完全信任您的输入并且无法以任何其他方式获得所需结果时才使用 eval;如果您正在评估的变量可能包含不受信任的用户可以创建的任何内容...比如说,/tmp 中的文件名,使用 eval 会让您自己面临痛苦、痛苦和安全漏洞)。
      • @CharlesDuffy:@CharlesDuffy:eval 是对的,但 printf %q 是 Bashism,问题是关于 Bourne Shell。
      • 谢谢!输入在这里是 100% 受信任的,所以这将完美运行。
      • 我遇到了同样的问题,我的脚本接收到的一些参数被传递给另一个脚本执行。使用eval 可能没问题,我想我可以过滤有害元素以防万一,但真的没有其他选择吗?例如,我尝试使用sed 转义空格,但这似乎不起作用。
      • @Haravikk,POSIX sh 中只有一个数组可用——"$@";它是本地范围的(因此可以通过在函数中进行任何操作来保留原始形式),并且可以通过使用set 进行修改。所以——有时在 POSIX sh 中可以对参数向量进行明确的操作,但这都是关于细节的。如果 bash 采用的 ksh 扩展不能实现没有它们就无法完成的事情,毕竟它们的存在没有多大意义。
      【解决方案5】:

      使用数组准确地指定参数列表,而不会出现字符串拆分(这就是这里的错误):

      args=( --foo "bar baz" qux )
      command "${args[@]}"
      

      如果您需要动态构建参数列表,可以使用+= 附加到数组:

      args=( )
      while ...; do
         args+=( "$another_argument" )
      done
      call_your_subprocess "${args[@]}"
      

      请注意,必须使用引号和[@] 而不是[*]

      【讨论】:

      • OP 正在询问 Bourne Shell(即/bin/sh)。对于bash,这将是一个很好的解决方案,但我不相信 bourne shell 支持数组,不是吗?
      • 谢谢!这可能是该程序切换到 bash 的原因。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-08-19
      • 2014-09-15
      • 1970-01-01
      • 2012-03-04
      • 2022-01-23
      • 2012-05-26
      • 1970-01-01
      相关资源
      最近更新 更多