我读到我应该在 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
这个有效:
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
这个没有(注意 $wget_options 周围的双引号):
wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
这是什么原因呢?
第一行是好版本吗?或者我应该怀疑某个地方存在导致此行为的隐藏错误?
一般来说,我在哪里可以找到好的文档来了解 bash 及其引用的工作原理?在编写这个脚本的过程中,我觉得我开始在试错的基础上工作,而不是理解规则。
答案1
最可靠的编码方法是使用数组:
wget_options=(
--mirror
--no-host-directories
--directory_prefix="$1"
)
wget "${wget_options[@]}" "$2/$3"
答案2
基本上,您应该用双引号变量扩展来保护它们免受分词(和文件名生成)的影响。然而,在你的例子中,
wget_options='--mirror --no-host-directories'
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
分词正是你想要的。
使用"$wget_options"
(引用),wget
不知道如何处理单个参数--mirror --no-host-directories
并抱怨
wget: unknown option -- mirror --no-host-directories
为了wget
将两个选项分开--mirror
并看到--no-host-directories
,必须进行分词。
有更强大的方法可以做到这一点。如果您正在使用bash
或任何其他像bash
do 这样使用数组的 shell,请参阅格伦·杰克曼的回答。吉尔斯的回答另外还描述了一种针对普通外壳(例如标准外壳)的替代解决方案/bin/sh
。两者本质上都将每个选项存储为数组中的单独元素。
具有良好答案的相关问题:为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?
双引号变量扩展是一个很好的经验法则。去做。然后要注意很少您不应该这样做的情况。这些将通过诊断消息呈现给您,例如上面的错误消息。
也有一些情况你不这样做需要引用变量扩展。但无论如何,继续使用双引号会更容易,因为它没有太大区别。其中一个案例是
variable=$other_variable
另一种是
case $variable in
...) ... ;;
esac
答案3
您正在尝试将字符串列表存储在字符串变量中。它不适合。无论您如何访问该变量,总会有一些东西被破坏。
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[@]}" …
在普通 sh 中,没有数组变量。如果您不介意丢失位置参数,则可以使用它们来存储一个字符串列表。
set -- --mirror --no-host-directories --user-agent="I can haz spaces"
wget "$@" …
有关更多信息,请参阅为什么我的 shell 脚本会因为空格或其他特殊字符而卡住?