我设法在我正在处理的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"
为什么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
不会提供此
答案 0 :(得分:18)
因为当您尝试使用
执行命令时$cmd
只发生一层扩张。 $cmd
包含echo "hello this is a test"
,它被扩展为6个以空格分隔的标记:
echo
"hello
this
is
a
test"
这就是set -x
输出显示的内容:它在包含双引号的标记周围放置单引号,以便清楚单个标记的内容。
如果您希望将$cmd
扩展为一个字符串,然后再次应用所有bash引用规则,请尝试执行以下命令:
bash -c "$cmd"
或(正如@bitmask在评论中指出的那样,这可能更有效)
eval "$cmd"
而不仅仅是
$cmd
答案 1 :(得分:2)
使用Bash arrays来实现您想要的行为,而不是诉诸非常危险的行为(见下文)eval
和bash -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会尝试执行相同的&#34;极低安全性&#34;逃避特殊字符:
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问题类似,我们希望动态构造传递给排序的--output=
参数,但由于Bash的自动,我们必须(必须?不是真的)依赖eval
引用&#34;安全&#34;特征
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
的项目子目录,该脚本实际上会运行以下一系列命令:
echo $'3\n2\n1'
sort -n --output=owned.txt; touch hahaha.txt; echo .txt
如您所见,这完全不是我们想要的。但是你可能会问,在这个人为的例子中,用户不可能创建项目目录owned.txt; touch hahaha.txt; echo
,如果他们可以,我们是否已经遇到麻烦了?
可能,但是脚本解析的不是当前目录名,而是远程git
源代码库分支的名称呢?除非您计划极其努力地限制或清理脚本使用其名称,标识符或其他数据的每个用户控制的工件,否则请远离eval
。