z32*_*7ul 18 shell bash options quoting variable
我读到我应该在 bash 中引用变量,例如“$foo”而不是 $foo。但是,在编写脚本时,我发现了一个案例,它没有引号但不能使用引号:
wget_options='--mirror --no-host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line
relative_path="$3" # /XXX received from command line
Run Code Online (Sandbox Code Playgroud)
这个有效:
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
Run Code Online (Sandbox Code Playgroud)
这个没有(注意 $wget_options 周围的双引号):
wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
Run Code Online (Sandbox Code Playgroud)
这是什么原因?
第一行是不是好版本;还是我应该怀疑某处存在导致这种行为的隐藏错误?
一般来说,我在哪里可以找到好的文档来了解 bash 及其引用的工作原理?在编写这个脚本的过程中,我觉得我开始在试错的基础上工作,而不是理解规则。
gle*_*man 33
最健壮的编码方式是使用数组:
wget_options=(
--mirror
--no-host-directories
--directory_prefix="$1"
)
wget "${wget_options[@]}" "$2/$3"
Run Code Online (Sandbox Code Playgroud)
Kus*_*nda 28
基本上,您应该双引号变量扩展以保护它们免于分词(和文件名生成)。但是,在您的示例中,
wget_options='--mirror --no-host-directories'
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
Run Code Online (Sandbox Code Playgroud)
分词正是您想要的。
With "$wget_options"(quoted),wget不知道如何处理单个参数--mirror --no-host-directories并抱怨
wget: unknown option -- mirror --no-host-directories
Run Code Online (Sandbox Code Playgroud)
对于wget看到两个选项--mirror,并--no-host-directories作为单独的,词的拆分有发生。
有更强大的方法可以做到这一点。如果您正在使用bash或任何其他使用数组的 shell bash,请参阅glenn jackman 的回答。Gilles 的回答还描述了一种用于更简单外壳的替代解决方案,例如标准/bin/sh. 两者本质上都将每个选项存储为数组中的单独元素。
相关问题有很好的答案:为什么我的 shell 脚本会因空格或其他特殊字符而阻塞?
双引号变量扩展是一个很好的经验法则。这样做。然后注意极少数情况下您不应该这样做。这些将通过诊断消息(例如上述错误消息)呈现给您。
还有一些情况不需要引用变量扩展。但无论如何继续使用双引号更容易,因为它没有太大区别。一种这样的情况是
variable=$other_variable
Run Code Online (Sandbox Code Playgroud)
另一个是
case $variable in
...) ... ;;
esac
Run Code Online (Sandbox Code Playgroud)
Gil*_*il' 17
您正在尝试将字符串列表存储在字符串变量中。它不适合。无论您如何访问变量,都会出现问题。
wget_options='--mirror --no-host-directories'将变量设置为wget_options包含空格的字符串。在这一点上,没有办法知道空格应该是选项的一部分,还是选项之间的分隔符。
当您使用带引号的替换访问变量时,变量wget "$wget_options"的值将用作字符串。这意味着它作为单个参数传递给wget,因此它是一个选项。这在您的情况下会中断,因为您打算将其表示为多个选项。
当您使用不带引号的替换时wget $wget_options,字符串变量的值会经历一个昵称为“split+glob”的扩展过程:
$IFS变量)。这会产生一个中间字符串列表。这恰好适用于您的示例,因为拆分过程将空格转换为分隔符,但通常不起作用,因为选项可能包含空格和通配符。
在 ksh、bash、yash 和 zsh 中,您可以使用数组变量。shell 术语中的数组是字符串列表,因此不会丢失信息。要创建数组变量,请在为变量赋值时在数组元素周围加上括号。要访问数组的所有元素,请使用- 这是 的泛化,它从数组的元素形成一个列表。请注意,这里也需要双引号,否则每个元素都会经历 split+glob。"${VARIABLE[@]}""$@"
wget_options=(--mirror --no-host-directories --user-agent="I can haz spaces")
wget "${wget_options[@]}" …
Run Code Online (Sandbox Code Playgroud)
在普通 sh 中,没有数组变量。如果您不介意丢失位置参数,您可以使用它们来存储一个字符串列表。
set -- --mirror --no-host-directories --user-agent="I can haz spaces"
wget "$@" …
Run Code Online (Sandbox Code Playgroud)
有关更多信息,请参阅为什么我的 shell 脚本会因空格或其他特殊字符而阻塞?