Linux Bash在执行前将引号插入字符串
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6087494/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Bash inserting quotes into string before execution
提问by Adam Charnock
I have managed to track done a weird problem in an init script I am working on. I have simplified the problem down in the following example:
我设法在我正在处理的初始化脚本中跟踪完成了一个奇怪的问题。我在以下示例中简化了问题:
> 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"
Why is bash inserting those extra single quotes into the executed command?
为什么 bash 将那些额外的单引号插入到执行的命令中?
The extra quotes don't cause any problems in the above example, but they are really giving me a headache.
在上面的例子中,额外的引号不会引起任何问题,但它们确实让我头疼。
For the curious, the actual problem code is:
对于好奇,实际问题代码是:
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"
Which produces this:
产生这个:
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
And therefore:
因此:
/bin/su: invalid option -- 'f'
Note: I am using the su
command here as I need to ensure the user's virtualenv is setup before celeryd is run. --chuid
will not provide this
注意:我在su
这里使用命令是因为我需要确保在运行 celeryd 之前设置用户的 virtualenv。--chuid
不会提供这个
采纳答案by Matthew Slattery
Because when you try to execute your command with
因为当您尝试执行命令时
$cmd
only one layer of expansion happens. $cmd
contains echo "hello this is a test"
, which is expanded into 6 whitespace-separated tokens:
只有一层扩展发生。 $cmd
contains echo "hello this is a test"
,它被扩展为 6 个以空格分隔的标记:
echo
"hello
this
is
a
test"
echo
"hello
this
is
a
test"
and that's what the set -x
output is showing you: it's putting single quotes around the tokens that contain double quotes, in order to be clear about what the individual tokens are.
这就是set -x
输出显示的内容:它在包含双引号的标记周围加上单引号,以便清楚各个标记是什么。
If you want $cmd
to be expanded into a string which then has all the bash quoting rules applied again, try executing your command with:
如果您想$cmd
扩展为一个字符串,然后再次应用所有 bash 引用规则,请尝试使用以下命令执行您的命令:
bash -c "$cmd"
or (as @bitmask points out in the comments, and this is probably more efficient)
或(正如@bitmask 在评论中指出的那样,这可能更有效)
eval "$cmd"
instead of just
而不仅仅是
$cmd
回答by Dejay Clayton
Use Bash arraysto achieve the behavior you want, without resorting to the very dangerous (see below) eval
and bash -c
.
使用 Bash数组来实现您想要的行为,而无需求助于非常危险的(见下文)eval
和bash -c
.
Using arrays:
使用数组:
declare -a CMD=(echo --test-arg \"Hello\ there\ friend\")
set -x
echo "${CMD[@]}"
"${CMD[@]}"
outputs:
输出:
+ echo echo --test-arg '"Hello there friend"'
echo --test-arg "Hello there friend"
+ echo --test-arg '"Hello there friend"'
--test-arg "Hello there friend"
Be careful to ensure that your array invocation is wrapped by double-quotes; otherwise, Bash tries to perform the same "bare-minimum safety" escaping of special characters:
注意确保你的数组调用被双引号包裹;否则,Bash 会尝试执行相同的“最低限度安全”转义特殊字符:
declare -a CMD=(echo --test-arg \"Hello\ there\ friend\")
set -x
echo "${CMD[@]}"
${CMD[@]}
outputs:
输出:
+ echo echo --test-arg '"Hello there friend"'
echo --test-arg "Hello there friend"
+ echo --test-arg '"Hello' there 'friend"'
--test-arg "Hello there friend"
ASIDE: Why is eval
dangerous?
旁白:为什么是eval
危险的?
eval
is only safe if you can guarantee that every input passed to it will not unexpectedly change the way that the command under eval
works.
eval
只有当您能保证传递给它的每个输入都不会意外地改变命令的eval
工作方式时,它才是安全的。
Example: As a totally contrived example, let's say we have a script that runs as part of our automated code deployment process. The script sorts some input (in this case, three lines of hardcoded text), and outputs the sorted text to a file whose named is based on the current directory name. Similar to the original SO question posed here, we want to dynamically construct the --output=
parameter passed to sort, but we must (must? not really) rely on eval
because of Bash's auto-quoting "safety" feature.
示例:作为一个完全人为设计的示例,假设我们有一个脚本作为我们自动化代码部署过程的一部分运行。该脚本对一些输入(在本例中为三行硬编码文本)进行排序,并将排序后的文本输出到一个基于当前目录名称命名的文件中。与此处提出的原始 SO 问题类似,我们希望动态构造--output=
传递给 sort的参数,但eval
由于 Bash 的自动引用“安全”功能,我们必须(必须?不是真的)依赖。
echo $'3\n2\n1' | eval sort -n --output="$(pwd | sed 's:.*/::')".txt
Running this script in the directory /usr/local/deploy/project1/
results in a new file being created at /usr/local/deploy/project1/project1.txt
.
在目录中运行此脚本会/usr/local/deploy/project1/
导致在/usr/local/deploy/project1/project1.txt
.
So somehow, if a user were to create a project subdirectory named owned.txt; touch hahaha.txt; echo
, the script would actually run the following series of commands:
所以不知何故,如果用户要创建一个名为 的项目子目录owned.txt; touch hahaha.txt; echo
,脚本实际上会运行以下一系列命令:
echo $'3\n2\n1'
sort -n --output=owned.txt; touch hahaha.txt; echo .txt
As you can see, that's totally not what we want. But you may ask, in this contrived example, isn't it unlikely that the user could create a project directory owned.txt; touch hahaha.txt; echo
, and if they could, aren't we already in trouble already?
如您所见,这完全不是我们想要的。但是你可能会问,在这个人为的例子中,用户是不是不太可能创建一个项目目录owned.txt; touch hahaha.txt; echo
,如果他们可以,我们不是已经遇到麻烦了吗?
Maybe, but what about a scenario where the script is parsing not the current directory name, but instead the name of a remote git
source code repository branch? Unless you plan to be extremely diligent about restricting or sanitizing every user-controlled artifact whose name, identifier, or other data is used by your script, stay well clear of eval
.
也许吧,但是如果脚本不是解析当前目录名称,而是解析远程git
源代码存储库分支的名称,那该怎么办?除非您计划非常努力地限制或清理每个用户控制的工件,其名称、标识符或其他数据被您的脚本使用,否则请远离eval
.