Tim*_*Tim 104 shell bash quoting variable
$ ls -l /tmp/test/my\ dir/
total 0
Run Code Online (Sandbox Code Playgroud)
我想知道为什么以下运行上述命令的方法失败或成功?
$ abc='ls -l "/tmp/test/my dir"'
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
$ bash -c $abc
'my dir'
$ bash -c "$abc"
total 0
$ eval $abc
total 0
$ eval "$abc"
total 0
Run Code Online (Sandbox Code Playgroud)
ilk*_*chu 149
这已经在 unix.SE 上的许多问题中讨论过,我会尽量收集我能在这里提出的所有问题。参考在最后。
您面临这些问题的原因是分词以及从变量扩展的引号不充当引号,而只是普通字符的事实。
问题中提出的案例:
此处的分配将单个字符串分配ls -l "/tmp/test/my dir"给abc:
$ abc='ls -l "/tmp/test/my dir"'
Run Code Online (Sandbox Code Playgroud)
下面,$abc在空格上分割,并ls获得三个参数-l, "/tmp/test/myand dir"(第二个前面有一个引号,第三个后面有另一个)。该选项有效,但路径处理不正确:
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
Run Code Online (Sandbox Code Playgroud)
在这里,扩展被引用,所以它被保留为一个单词。shell 会尝试查找字面上称为ls -l "/tmp/test/my dir"、包含空格和引号的程序。
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
Run Code Online (Sandbox Code Playgroud)
在这里,$abc被拆分,并且只有第一个结果单词被作为 的参数-c,所以 Bash 只是ls在当前目录中运行。其他的话参数庆典,并用于填充$0,$1等等。
$ bash -c $abc
'my dir'
Run Code Online (Sandbox Code Playgroud)
使用bash -c "$abc", 和eval "$abc",还有一个额外的 shell 处理步骤,它确实使引号起作用,但也会导致再次处理所有 shell 扩展,因此存在意外运行的风险,例如从用户提供的数据中替换命令,除非您是引用时非常小心。
存储命令的两种更好的方法是 a) 使用函数代替,b) 使用数组变量(或位置参数)。
使用函数:
只需在里面声明一个带有命令的函数,然后像运行命令一样运行该函数。函数内命令的扩展仅在命令运行时处理,而不是在定义时处理,并且您不需要引用单个命令。
# define it
myls() {
ls -l "/tmp/test/my dir"
}
# run it
myls
Run Code Online (Sandbox Code Playgroud)
使用数组:
数组允许创建多词变量,其中单个词包含空格。在这里,单个单词存储为不同的数组元素,"${array[@]}"扩展将每个元素扩展为单独的 shell 单词:
# define the array
mycmd=(ls -l "/tmp/test/my dir")
# run the command
"${mycmd[@]}"
Run Code Online (Sandbox Code Playgroud)
语法有点可怕,但数组还允许您逐个构建命令行。例如:
mycmd=(ls) # initial command
if [ "$want_detail" = 1 ]; then
mycmd+=(-l) # optional flag
fi
mycmd+=("$targetdir") # the filename
"${mycmd[@]}"
Run Code Online (Sandbox Code Playgroud)
或者保持命令行的一部分不变并使用数组填充其中的一部分,例如选项或文件名:
options=(-x -v)
files=(file1 "file name with whitespace")
target=/somedir
transmutate "${options[@]}" "${files[@]}" "$target"
Run Code Online (Sandbox Code Playgroud)
数组的缺点是它们不是标准功能,因此普通 POSIX shell(例如Debian/Ubuntu 中dash的默认值/bin/sh)不支持它们(但请参见下文)。但是,Bash、ksh 和 zsh 可以,因此您的系统可能有一些支持数组的 shell。
使用 "$@"
在不支持命名数组的 shell 中,仍然可以使用位置参数(伪数组"$@")来保存命令的参数。
以下应该是可移植的脚本位,它们与上一节中的代码位等效。该数组被替换"$@"为位置参数列表。设置"$@"是用 完成的set,周围的双引号"$@"很重要(这些会导致列表的元素被单独引用)。
首先,简单地存储一个带参数的命令"$@"并运行它:
set -- ls -l "/tmp/test/my dir"
"$@"
Run Code Online (Sandbox Code Playgroud)
有条件地为命令设置部分命令行选项:
set -- ls
if [ "$want_detail" = 1 ]; then
set -- "$@" -l
fi
set -- "$@" "$targetdir"
"$@"
Run Code Online (Sandbox Code Playgroud)
仅"$@"用于选项和操作数:
set -- -x -v
set -- "$@" file1 "file name with whitespace"
set -- "$@" /somedir
transmutate "$@"
Run Code Online (Sandbox Code Playgroud)
(当然,"$@"通常会填充脚本本身的参数,因此您必须在重新调整用途之前将它们保存在某处"$@"。)
eval(这里要小心!)eval接受一个字符串并将其作为命令运行,就像在 shell 命令行中输入一样。这包括所有引用和扩展处理,这既有用又危险。
在简单的情况下,它允许做我们想做的事情:
cmd='ls -l "/tmp/test/my dir"'
eval "$cmd"
Run Code Online (Sandbox Code Playgroud)
随着eval,引号被处理,所以ls最终只看到两个参数-land /tmp/test/my dir,就像我们想要的那样。eval也足够聪明,可以连接它得到的任何参数,所以eval $cmd在某些情况下也可以工作,但例如所有的空白运行都将更改为单个空格。最好在那里引用变量,因为这将确保它不会被修改为eval.
但是,将用户输入包含在命令字符串中是很危险的eval。例如,这似乎有效:
read -r filename
cmd="ls -ld '$filename'"
eval "$cmd";
Run Code Online (Sandbox Code Playgroud)
但是如果用户提供包含单引号的输入,他们可以跳出引号并运行任意命令!例如,使用 input '$(whatever)'.txt,您的脚本愉快地运行命令替换。它本来可以rm -rf(或更糟)代替。
问题在于 的值$filename嵌入在eval运行的命令行中。它之前被扩展过eval,可以看到例如命令ls -l ''$(whatever)'.txt'。您需要对输入进行预处理以确保安全。
如果我们以另一种方式执行,将文件名保留在变量中,并让eval命令扩展它,则再次更安全:
read -r filename
cmd='ls -ld "$filename"'
eval "$cmd";
Run Code Online (Sandbox Code Playgroud)
请注意,外部引号现在是单引号,因此不会发生内部扩展。因此,eval查看命令ls -l "$filename"并安全地扩展文件名本身。
但这与仅将命令存储在函数或数组中没有太大区别。对于函数或数组,则没有这样的问题,因为单词一直保持独立,并且没有对filename.
read -r filename
cmd=(ls -ld -- "$filename")
"${cmd[@]}"
Run Code Online (Sandbox Code Playgroud)
几乎唯一使用的原因eval是其中的变化部分涉及无法通过变量(管道、重定向等)引入的 shell 语法元素。即便如此,请确保不要将用户的输入嵌入到eval命令中!
Hau*_*ing 11
运行(非平凡)命令的最安全方法是eval. 然后,您可以像在命令行上一样编写该命令,并且它的执行就像您刚刚输入它一样。但你必须引用一切。
简单案例:
abc='ls -l "/tmp/test/my dir"'
eval "$abc"
Run Code Online (Sandbox Code Playgroud)
不是那么简单的情况:
# command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
abc='awk '\''! a[$0]++ { print "foo: " $0; }'\'' inputfile'
eval "$abc"
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
129761 次 |
| 最近记录: |