为什么带引号的变量中的选项会失败,但不带引号的变量却可以工作?

为什么带引号的变量中的选项会失败,但不带引号的变量却可以工作?

我读到我应该在 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或任何其他像bashdo 这样使用数组的 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”的扩展过程:

  1. 获取变量的值并将其拆分为以空格分隔的部分(假设您尚未修改该$IFS变量)。这会产生一个中间字符串列表。
  2. 对于中间列表的每个元素,如果它是与一个或多个文件匹配的通配符模式,则将该元素替换为匹配文件的列表。

这恰好在您的示例中起作用,因为拆分过程将空格转换为分隔符,但通常不起作用,因为选项可能包含空格和通配符。

在 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 脚本会因为空格或其他特殊字符而卡住?

相关内容