繁体   English   中英

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

[英]Bash inserting quotes into string before execution

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

> 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"

为什么 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"

产生这个:

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

因此:

/bin/su: invalid option -- 'f'

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

因为当您尝试执行命令时

$cmd

只有一层扩展发生。 $cmd包含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"

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

eval "$cmd"

而不仅仅是

$cmd

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

使用数组:

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

输出:

+ echo echo --test-arg '"Hello there friend"'
echo --test-arg "Hello there friend"
+ echo --test-arg '"Hello there friend"'
--test-arg "Hello there friend"

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

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

输出:

+ echo echo --test-arg '"Hello there friend"'
echo --test-arg "Hello there friend"
+ echo --test-arg '"Hello' there 'friend"'
--test-arg "Hello there friend"

旁白:为什么eval是危险的?

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

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

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

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

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

echo $'3\n2\n1'
sort -n --output=owned.txt; touch hahaha.txt; echo .txt

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

也许吧,但是如果脚本不是解析当前目录名称,而是解析远程git源代码存储库分支的名称,那该怎么办? 除非您计划非常努力地限制或清理每个用户控制的工件,这些工件的名称、标识符或其他数据被您的脚本使用,否则请远离eval

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM