执行时变量需要加引号吗?

执行时变量需要加引号吗?

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)。

另请参见例如

答案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[@]}扩展是否被引用:

  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

答案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,您也将面临任意代码执行。

相关内容