Bash在执行前将引号插入字符串

Ada*_*ock 18 linux bash shell

我设法在我正在处理的init脚本中跟踪完成了一个奇怪的问题.我在以下示例中简化了问题:

> set -x                           # <--- Make Bash show the commands it runs
> cmd="echo \"hello this is a test\""
+ cmd='echo "hello this is a test"'
> $cmd
+ echo '"hello' this is a 'test"'  # <--- Where have the single quotes come from?
"hello this is a test"
Run Code Online (Sandbox Code Playgroud)

为什么bash将这些额外的单引号插入到执行的命令中?

额外的引号在上面的例子中没有引起任何问题,但它们真的让我头疼.

对于好奇,实际的问题代码是:

cmd="start-stop-daemon --start $DAEMON_OPTS \
    --quiet \
    --oknodo \
    --background \
    --make-pidfile \
    $* \
    --pidfile $CELERYD_PID_FILE
    --exec /bin/su -- -c \"$CELERYD $CELERYD_OPTS\" - $CELERYD_USER"
Run Code Online (Sandbox Code Playgroud)

产生这个:

start-stop-daemon --start --chdir /home/continuous/ci --quiet --oknodo --make-pidfile --pidfile /var/run/celeryd.pid --exec /bin/su -- -c '"/home/continuous/ci/manage.py' celeryd -f /var/log/celeryd.log -l 'INFO"' - continuous
Run Code Online (Sandbox Code Playgroud)

因此:

/bin/su: invalid option -- 'f'
Run Code Online (Sandbox Code Playgroud)

注意:我在su这里使用命令,因为我需要确保在运行celeryd之前设置用户的virtualenv.--chuid不会提供这个

Mat*_*ery 20

因为当你尝试用你的命令执行时

$cmd
Run Code Online (Sandbox Code Playgroud)

只发生一层扩张. $cmdcontains echo "hello this is a test",扩展为6个以空格分隔的标记:

  1. echo
  2. "hello
  3. this
  4. is
  5. a
  6. test"

这就是set -x输出显示的内容:它在包含双引号的标记周围加上单引号,以便清楚单个标记是什么.

如果要$cmd扩展为一个字符串,然后再次应用所有bash引用规则,请尝试执行以下命令:

bash -c "$cmd"
Run Code Online (Sandbox Code Playgroud)

或者(正如@bitmask在评论中指出的那样,这可能更有效)

eval "$cmd"
Run Code Online (Sandbox Code Playgroud)

而不仅仅是

$cmd
Run Code Online (Sandbox Code Playgroud)

  • `eval`也应该这样做,不应该吗? (3认同)
  • 对于任何使用“eval”或通过“bash -c”执行命令的建议要非常非常警惕。您正在为自己和脚本的用户暴露一些不明显的安全漏洞。 (2认同)

Dej*_*ton 5

使用 Bash数组来实现您想要的行为,而无需诉诸非常危险的(见下文)evalbash -c.

使用数组:

declare -a CMD=(echo --test-arg \"Hello\ there\ friend\")
set -x
echo "${CMD[@]}"
"${CMD[@]}"
Run Code Online (Sandbox Code Playgroud)

输出:

+ echo echo --test-arg '"Hello there friend"'
echo --test-arg "Hello there friend"
+ echo --test-arg '"Hello there friend"'
--test-arg "Hello there friend"
Run Code Online (Sandbox Code Playgroud)

请小心确保数组调用用双引号引起来;否则,Bash 会尝试对特殊字符执行相同的“最低限度安全”转义:

declare -a CMD=(echo --test-arg \"Hello\ there\ friend\")
set -x
echo "${CMD[@]}"
${CMD[@]}
Run Code Online (Sandbox Code Playgroud)

输出:

+ echo echo --test-arg '"Hello there friend"'
echo --test-arg "Hello there friend"
+ echo --test-arg '"Hello' there 'friend"'
--test-arg "Hello there friend"
Run Code Online (Sandbox Code Playgroud)

旁白:为什么eval危险?

eval仅当您可以保证传递给它的每个输入都不会意外改变命令的工作方式时,才是安全的eval

示例:作为一个完全人为的示例,假设我们有一个作为自动化代码部署过程的一部分运行的脚本。该脚本对一些输入(在本例中为三行硬编码文本)进行排序,并将排序后的文本输出到一个文件,该文件的命名基于当前目录名称。与这里提出的原始 SO 问题类似,我们想要动态构造传递给排序的参数,但由于 Bash 的自动引用“安全”功能,--output=我们必须(必须?不是真的)依赖。eval

echo $'3\n2\n1' | eval sort -n --output="$(pwd | sed 's:.*/::')".txt
Run Code Online (Sandbox Code Playgroud)

在目录中运行此脚本/usr/local/deploy/project1/会在 处创建一个新文件/usr/local/deploy/project1/project1.txt

因此,如果用户要创建一个名为 的项目子目录owned.txt; touch hahaha.txt; echo,脚本实际上会运行以下一系列命令:

echo $'3\n2\n1'
sort -n --output=owned.txt; touch hahaha.txt; echo .txt
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,这完全不是我们想要的。但您可能会问,在这个人为的示例中,用户是否不太可能创建项目目录owned.txt; touch hahaha.txt; echo,如果他们可以,我们不是已经遇到麻烦了吗?

也许吧,但是如果脚本解析的不是当前目录名称,而是远程git源代码存储库分支的名称,情况又如何呢?除非您打算非常努力地限制或清理脚本使用的每个用户控制的工件(其名称、标识符或其他数据),否则请远离eval.