变量在执行时应该被引用吗?

ter*_*don 18 shell-script quoting variable

shell 脚本中的一般规则是,除非有令人信服的理由,否则应该始终引用变量。有关您可能想知道的更多详细信息,请查看这个很棒的问答:忘记在 bash/POSIX shell 中引用变量的安全隐患

但是,请考虑如下函数:

run_this(){
    $@
}
Run Code Online (Sandbox Code Playgroud)

应该$@在那里引用还是不引用?我玩了一会儿,找不到任何缺少引号引起问题的情况。另一方面,在传递包含空格的命令作为引用变量时,使用引号会使它中断:

#!/usr/bin/sh
set -x
run_this(){
    $@
}
run_that(){
    "$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"
Run Code Online (Sandbox Code Playgroud)

运行上面的脚本返回:

$ 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 Code Online (Sandbox Code Playgroud)

如果我使用run_that $comm而不是,我可以解决这个问题run_that "$comm",但是因为run_this(未加引号)函数同时适用于两者,这似乎是更安全的选择。

那么,在特定情况下$@在其工作是$@作为命令执行的函数中使用时,应该$@引用吗?请解释为什么应该/不应该引用它,并给出一个可以破坏它的数据示例。

mur*_*uru 20

问题在于如何将命令传递给函数:

$ 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
Run Code Online (Sandbox Code Playgroud)

"$@"应该在您的run_this函数以正常编写的命令为前缀的一般情况下使用。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
Run Code Online (Sandbox Code Playgroud)

我不确定应该如何将带空格的文件名传递给run_this.

  • 不,这确实是一种习惯,以至于我(错误地)对其进行了测试并得出结论“嗯,也许这个不需要引号”。一种通常称为脑放屁的过程。 (2认同)

Sté*_*las 9

它是:

interpret_this_shell_code() {
  eval "$1"
}
Run Code Online (Sandbox Code Playgroud)

或者:

interpret_the_shell_code_resulting_from_the_concatenation_of_those_strings_with_spaces() {
  eval "$@"
}
Run Code Online (Sandbox Code Playgroud)

或者:

execute_this_simple_command_with_these_arguments() {
  "$@"
}
Run Code Online (Sandbox Code Playgroud)

但:

execute_the_simple_command_with_the_arguments_resulting_from_split+glob_applied_to_these_strings() {
  $@
}
Run Code Online (Sandbox Code Playgroud)

没有多大意义。

如果你想执行ls -l命令(不是ls带有ls-l作为参数的命令),你会这样做:

interpret_this_shell_code '"ls -l"'
execute_this_simple_command_with_these_arguments 'ls -l'
Run Code Online (Sandbox Code Playgroud)

但是,如果(更有可能)是ls带有ls-l作为参数的命令,您将运行:

interpret_this_shell_code 'ls -l'
execute_this_simple_command_with_these_arguments ls -l
Run Code Online (Sandbox Code Playgroud)

现在,如果你想执行的不仅仅是一个简单的命令,如果你想做变量赋值、重定向、管道……,只会interpret_this_shell_code做:

interpret_this_shell_code 'ls -l 2> /dev/null'
Run Code Online (Sandbox Code Playgroud)

当然,你总是可以这样做:

execute_this_simple_command_with_these_arguments eval '
  ls -l 2> /dev/null'
Run Code Online (Sandbox Code Playgroud)


PSk*_*cik 5

看着它从在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
Run Code Online (Sandbox Code Playgroud)

通过$*/${a[*]}扩展,您可以将数组与第一个值连接起来IFS(默认为空格)连接成一个巨大的字符串。如果你不引用它,它会像普通字符串一样被拆分。

对于$@/${a[@]}扩展,行为取决于$@/${a[@]}扩展是否被引用:

  1. 如果它被引用("$@""${a[@]}"),你会得到等价的 "$1" "$2" "$3" #..."${a[1]}" "${a[2]}" "${a[3]}" # ...
  2. 如果它没有被引用($@${a[@]}),你会得到等价的 $1 $2 $3 #...${a[1]} ${a[2]} ${a[3]} # ...

对于包装命令,您绝对需要带引号的 @扩展 (1.)。


关于 bash(和类似 bash)数组的更多好信息:https : //lukeshu.com/blog/bash-arrays.html