【问题标题】:bash script execute command with double quotes, single quotes and spacesbash脚本用双引号、单引号和空格执行命令
【发布时间】:2020-04-06 10:07:19
【问题描述】:

我查看了有关此问题的类似帖子,但无法弄清楚如何使执行的代码采用正确的格式,该格式需要为foo --bar "a='b'"。我最好的尝试是

#!/bin/bash -x

bar='--bar ''"''a='"'"'b'"'"'"'
cmd=(foo $bar)
echo ${cmd[@]}
eval ${cmd[@]}

echo 的输出是正确的,但 eval 的输出不正确

+ bar='--bar "a='\''b'\''"'
+ cmd=(foo $bar)
+ echo foo --bar '"a='\''b'\''"'
foo --bar "a='b'"
+ eval foo --bar '"a='\''b'\''"'
++ foo --bar 'a='\''b'\'''

使用选项执行命令的正确方法是什么?

【问题讨论】:

  • cmd=(foo "$bar"),确定吗? (除非你有充分的理由引用变量扩展。)
  • 不要将复杂的参数列表存储为纯字符串,使用数组:bar=(--bar "a='b'"); foo "${bar[@]}"。并避免eval,这是一个巨大的脚炮。见this question
  • BashFAQ #50 作为一个整体是相关的。
  • 顺便说一句,注意--bar "a='b'" 通常是错误的——大多数命令都像--bar a='b' 一样使用,在这种情况下根本没有任何文字引号,而是只有 语法(a='b'a=b'a=b' 之间没有区别 whatsoever,因为在调用命令时它们都变成传递给 execve() 系统调用的相同 C 字符串数组这些参数被传递到的地方)。
  • ...顺便说一句,一旦你完成了 BashFAQ #50(如上链接),BashFAQ #48 也是相关的。

标签: bash shell


【解决方案1】:

如果您必须存储命令片段,请使用函数或数组,而不是字符串。

最佳实践代码示例,符合BashFAQ #50

#!/usr/bin/env bash
bar=( --bar a="b" )
cmd=(foo "${bar[@]}" )
printf '%q ' "${cmd[@]}" && echo  # print code equivalent to the command we're about to run
"${cmd[@]}"                       # actually run this code

奖励:您的调试输出并不能证明您的想法。

"a='b'"'a='\''b'\''' 是引用完全相同的字符串的两种不同方式。

为了证明这一点:

printf '%s\n' "a='b'" | md5sum -
printf '%s\n' 'a='\''b'\''' | md5sum -

...作为输出发射:

7f183df5823cf51ec42a3d4d913595d7  -
7f183df5823cf51ec42a3d4d913595d7  -

...所以在您的代码中解析echo $fooeval $foo 的参数的方式没有任何不同。

为什么这是真的?因为语法引号不是实际运行的命令的一部分;在 shell 使用它们来确定如何逐个字符地解释命令行后,它们会被 shell 删除。

那么,让我们分解一下set -x 向您展示的内容:

'a='\''b'\'''

...由以下串联在一起的文字字符串组成:

  • a=(在由单引号包围的单引号输入和结束的单引号上下文中)
  • '(在未引用的上下文中,由前面的反斜杠转义)
  • b(在由单引号包围的单引号输入和结束的单引号上下文中)
  • '(在未引用的上下文中)

...其他一切都是句法,对 shell 有意义,但从未传递给程序 foo

【讨论】:

    【解决方案2】:

    如果您希望发生与echo ${cmd[@]} 完全相同的扩展,只需运行以下命令:

    ${cmd[@]}
    

    它将执行:

    + foo --bar '"a='\''b'\''"'
    

    注意因为它没有被引用,例如*会根据filename expansion展开。

    【讨论】:

    • 你到底为什么要来回呢? printf '%q ' 会创建一个对eval 安全的字符串,当然,但最终结果与运行"${cmd[@]}" 完全相同,只是更慢且更复杂。
    • 相比之下,...和 ​​eval "${cmd[@]}" 只是错误,而您的原始公式并非如此。 eval 在解析该字符串之前将其所有参数连接到一个字符串中。
    • 如果你想要一个测试用例来证明这一点,试试cmd=( echo '* *' ) -- 在分配之后,"${cmd[@]}" 在输出时正确地发出* *,而eval "${cmd[@]}" 打印当前文件中的所有文件stdout 上的目录两次。
    • 为什么不引用 ${cmd[@]} ever 正确/可取?这样你会得到很多严重的错误——不仅仅是全局扩展。
    • 举个例子试试cmd=( printf ' - %s\n' 'hello world' 'goodbye world' );如果它没有被引用,那么输出......令人惊讶。
    猜你喜欢
    • 2016-01-22
    • 2013-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多