shell 脚本编写的一般规则是变量应始终被引用,除非有令人信服的理由不这样做。如需了解您可能想知道的更多详细信息,请查看以下精彩问答:忘记在 bash/POSIX shell 中引用变量的安全隐患。
但是,请考虑如下所示的函数:
run_this(){
$@
}
是否应该$@
在那里引用?我玩了一会儿,找不到任何因缺少引号而导致问题的情况。另一方面,使用引号会使它在传递包含空格作为带引号的变量的命令时中断:
#!/usr/bin/sh
set -x
run_this(){
$@
}
run_that(){
"$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"
运行上面的脚本返回:
$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users 0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found
run_that $comm
如果我使用而不是,我可以解决这个问题run_that "$comm"
,但由于run_this
(未引用的)函数适用于两者,所以这似乎是更安全的选择。
那么,在作为命令$@
执行的函数中使用的特定情况下,应该引用吗?请解释为什么应该/不应该引用它,并给出可能破坏它的数据示例。$@
$@
答案1
问题在于命令如何传递给函数:
$ run_this ls -l Untitled\ Document.pdf
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_that ls -l Untitled\ Document.pdf
-rw------- 1 muru muru 33879 Dec 20 11:09 Untitled Document.pdf
"$@"
as inrun_that
应该在一般情况下使用,其中您的函数以正常编写的命令为前缀(如上所述)。
尝试使用不带引号的字符$@
会run_this
导致无法传递带有空格的文件名。这些尝试都不会奏效:
$ run_this 'ls -l Untitled\ Document.pdf'
ls: cannot access Untitled\: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l "Untitled\ Document.pdf"'
ls: cannot access "Untitled\: No such file or directory
ls: cannot access Document.pdf": No such file or directory
$ run_this 'ls -l Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l' 'Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
它不起作用的原因是不带引号的扩展会经历单词分割,它会在任何空格上分割,并且不提供解释引号等的方法(为此,您需要使用eval
)。
另请参见例如
- Bash常见问题解答 050(或者“我试图将命令放入变量中,但复杂的情况总是失败!”)
- 我们如何运行存储在变量中的命令?
答案2
它是:
interpret_this_shell_code() {
eval "$1"
}
或者:
interpret_the_shell_code_resulting_from_the_concatenation_of_those_strings_with_spaces() {
eval "$@"
}
或者:
execute_this_simple_command_with_these_arguments() {
"$@"
}
但:
execute_the_simple_command_with_the_arguments_resulting_from_split+glob_applied_to_these_strings() {
$@
}
没有多大意义。
如果你想执行ls -l
命令(不是ls
带有ls
和-l
作为参数的命令),你可以这样做:
interpret_this_shell_code '"ls -l"'
execute_this_simple_command_with_these_arguments 'ls -l'
但如果(更有可能),这是ls
带有ls
和-l
作为参数的命令,您将运行:
interpret_this_shell_code 'ls -l'
execute_this_simple_command_with_these_arguments ls -l
现在,如果您想要执行的不仅仅是一个简单的命令,如果您想要进行变量分配、重定向、管道...,则只需interpret_this_shell_code
:
interpret_this_shell_code 'ls -l 2> /dev/null'
当然你总是可以这样做:
execute_this_simple_command_with_these_arguments eval '
ls -l 2> /dev/null'
答案3
从bash/ksh/zsh角度看,
$*
都是$@
一般数组扩展的特例。数组扩展与普通变量扩展不同:
$ a=("a b c" "d e" f)
$ printf ' -> %s\n' "${a[*]}"
-> a b c d e f
$ printf ' -> %s\n' "${a[@]}"
-> a b c
-> d e
-> f
$ printf ' -> %s\n' ${a[*]}
-> a
-> b
-> c
-> d
-> e
-> f
$ printf ' -> %s\n' ${a[@]}
-> a
-> b
-> c
-> d
-> e
-> f
通过$*
/${a[*]}
扩展,您可以将数组与第一个值IFS
(默认情况下为空格)连接成一个巨大的字符串。如果您不引用它,它会像普通字符串一样被分割。
对于$@
/${a[@]}
扩展,行为取决于$@
/${a[@]}
扩展是否被引用:
- 如果它被引用(
"$@"
或"${a[@]}"
),你会得到相当于"$1" "$2" "$3" #...
或"${a[1]}" "${a[2]}" "${a[3]}" # ...
- 如果它没有被引用(
$@
或${a[@]}
),你会得到相当于$1 $2 $3 #...
或${a[1]} ${a[2]} ${a[3]} # ...
对于包装命令,您绝对需要引用@扩展(1.)。
有关 bash(和类似 bash)数组的更多好信息:https://lukeshu.com/blog/bash-arrays.html
答案4
执行变量 inbash
是一种容易失败的技术。编写一个run_this
正确处理所有边缘情况的函数根本不可能,例如:
- 管道(例如
ls | grep filename
) - 输入/输出重定向(例如
ls > /dev/null
) if
while
诸如此类的 shell 语句
如果您只想避免代码重复,那么最好使用函数。例如,代替:
run_this(){
"$@"
}
command="ls -l"
...
run_this "$command"
你应该写
command() {
ls -l
}
...
command
如果这些命令仅在运行时可用,则应该使用eval
,它是专门为处理所有导致失败的怪癖而设计的run_this
:
command="ls -l | grep filename > /dev/null"
...
eval "$command"
注意eval
是已知的出于安全问题,但如果将变量从不受信任的来源传递到run_this
,您也将面临任意代码执行。