`set -- $args` 行在这里做什么,为什么它在 Zsh 和 Bash 之间的行为不同?

`set -- $args` 行在这里做什么,为什么它在 Zsh 和 Bash 之间的行为不同?

在 Mac OSX 附带版本的联机帮助页中getopt,给出了一个使用构造 的示例args=$(getopt optstring $*); set -- $args。这里做什么set -- $args

此外,考虑函数和测试字符串

f () {
    args=$(getopt o: $*)
    set -- $args
    for i; do
        echo $i
    done
}

f -o 123 xyz

在 Bash 3.2 和 4.3 中,这会产生

-o
123
--
xyz

但在 Zsh 5.1 中,它产生

 -o 123 -- xyz

造成差异的原因是什么?是因为 Zsh 不同的分词行为,还是其他原因?我尝试了引用和参数扩展标志的不同组合,但无法真正弄清楚任一 shell 中到底发生了什么。

答案1

这是糟糕的代码。getopt几乎无法使用。请使用getopts内置的 shell(它有两个优点:它可以工作,并且可以在所有 POSIX 平台上使用)。

所做args=$(getopt optstring $*); set -- $args的是:

  • 在空格处分割每个命令行参数,并将其替换为以空格分隔的单词列表。
  • 获取每个结果单词并将其解释为通配符模式。如果该模式至少匹配一个文件,则用匹配文件列表替换该模式。
  • 将生成的单词列表传递给getopt命令。
  • 将命令的输出存储getopt在变量中args。这是一个由命令行参数组成的字符串,以空格作为分隔符,并重新排序为选项及其参数,然后--是非选项操作数。
  • 在空格处拆分此字符串,将每个单词解释为通配符模式,并用匹配文件列表(如果有)替换该模式。
  • 使用结果列表作为位置参数。

在 Bourne/POSIX 风格的 shell 中,$foo是“split+glob”运算符。您需要双引号来获取变量的值:"$foo"。但在 zsh 中,工作方式有所不同:它扩展为except 当为空时$foo的值。所以在 zsh 中你会得到一个位置参数。foofoo

可以通过编写 来避免第一次使用 split+glob 运算符getopt optstring "$@":这会将位置参数精确地传递给getopt。但是在解析 的输出时getopt,拆分是不可避免的(不过您可以禁用通配符),因为getopt使用空格来分隔参数。问题在于getopt输出不明确:无法区分参数中的空格和getopt作为分隔符添加的空格。

执行此操作的正确方法是使用getopts.它坚固且便携。

while getopts optstring OPTLET; do
  # We got the option -$OPTLET
  case $OPTLET in
  esac
done
shift $((OPTIND-1))
# Now the positional parameters are just the non-option operands

¹至少是 BSD 版本。 GNU 版本添加了使其可用的功能。

答案2

set -- $args根据 的内容设置位置参数$args。现在您将获得zsh与其他 POSIX shell之间的不同行为。

因为zsh不执行场分裂默认情况下,你会得到一个字符串,它是 的内容$args。您必须显式调用 splitting 以获得与bash(以及其他 POSIX shell)相同的行为:

set -- ${=args}

bashField Splitting对 的内容执行$args,产生四个字符串。您可以检查$#以了解 后的位置参数的数量set -- $args

请注意,如果是bash,您set -f还应该添加以关闭通配符。

相关内容