getopt、getopts 或手动解析 - 当我想同时支持短选项和长选项时使用什么?

hel*_*hod 45 bash options shell-script user-interface getopts

目前我正在编写一个 Bash 脚本,它具有以下要求:

  • 它应该可以在各种 Unix/Linux 平台上运行
  • 它应该支持短和(GNU)长选项

我知道这getopts将是可移植性方面的首选方式,但 AFAIK 它不支持长选项。

getopt支持长选项,但BashGuide强烈建议反对它:

永远不要使用 getopt(1)。getopt 无法处理空参数字符串或带有嵌入空格的参数。请忘记它曾经存在过。

因此,仍然可以选择手动解析。这很容易出错,会产生相当多的样板代码,而且我需要自己处理错误(我想自己getopt(s)进行错误处理)。

那么,在这种情况下,首选是什么?

l0b*_*0b0 28

getoptvsgetopts似乎是一个宗教问题。至于反对的论点getopt猛砸常见问题

  • getopt无法处理空参数字符串”似乎指的是可选参数的已知问题,它看起来getopts根本不支持(至少从help getoptsBash 4.2.24 的阅读来看)。来自man getopt

    getopt(3) 可以解析带有可选参数的长选项,这些可选参数被赋予一个空的可选参数(但对于短选项不能这样做)。此 getopt(1) 将空的可选参数视为不存在。

我不知道“getopt无法处理带有嵌入空格的 [...] 参数”来自哪里,但让我们测试一下:

  • 测试.sh:

    #!/usr/bin/env bash
    set -o errexit -o noclobber -o nounset -o pipefail
    params="$(getopt -o ab:c -l alpha,bravo:,charlie --name "$0" -- "$@")"
    eval set -- "$params"
    
    while true
    do
        case "$1" in
            -a|--alpha)
                echo alpha
                shift
                ;;
            -b|--bravo)
                echo "bravo=$2"
                shift 2
                ;;
            -c|--charlie)
                echo charlie
                shift
                ;;
            --)
                shift
                break
                ;;
            *)
                echo "Not implemented: $1" >&2
                exit 1
                ;;
        esac
    done
    
    Run Code Online (Sandbox Code Playgroud)
  • 跑:

    $ ./test.sh -
    $ ./test.sh -acb '   whitespace   FTW   '
    alpha
    charlie
    bravo=   whitespace   FTW   
    $ ./test.sh -ab '' -c
    alpha
    bravo=
    charlie
    $ ./test.sh --alpha --bravo '   whitespace   FTW   ' --charlie
    alpha
    bravo=   whitespace   FTW   
    charlie
    
    Run Code Online (Sandbox Code Playgroud)

对我来说看起来像是 check and mate,但我相信有人会告诉我我是如何完全误解这句话的。当然,便携性问题仍然存在;您必须决定在具有较旧或没有 Bash 的平台上投入多少时间是值得的。我自己的建议是使用YAGNIKISS指南 - 仅针对您知道将要使用的特定平台进行开发。随着开发时间的延长,Shell 代码的可移植性通常会达到 100%。

  • OP 提到需要可移植到许多 Unix 平台,而您在此处引用的 `getopt` 是特定于 Linux 的。请注意,`getopt` 不是`bash` 的一部分,它甚至不是一个 GNU 实用程序,并且在 Linux 上随 util-linux 软件包一起提供。 (16认同)
  • `getopt` 是一个传统的命令,早在 Linux 发布之前就来自 System V。`getopt` 从未标准化。POSIX、Unix 或 Linux (LSB) 都没有标准化 `getopt` 命令。`getopts` 在所有三个中都有指定,但不支持长选项。 (10认同)
  • 大多数平台都有`getopt`,只有Linux AFAIK 带有一个支持长选项或参数中的空格。其他的只支持 System V 语法。 (3认同)
  • 补充一点:这不是传统的`getopt`。这是@StéphaneChazelas 指出的 linux-utils 风格。它具有将禁用上述语法的遗留选项,特别是联机帮助页指出“GETOPT_COMPATIBLE 强制 getopt 使用 SYNOPSIS 中指定的第一个调用格式”。但是,如果您可以期望目标系统安装此软件包,这完全是要走的路,因为原始 getopt 很糟糕,而 Bash 的 getopt 非常有限 (2认同)
  • OP 的链接声称“传统版本的 getopt 无法处理空参数字符串或嵌入空格的参数”。并且不应使用 util-linux 版本的 getopt 没有证据并且不再准确。一项快速调查(5 年多后)显示,ArchLinux、SUSE、Ubuntu、RedHat、Fedora 和 CentOS(以及大多数衍生变体)提供的默认 getopt 版本都支持可选参数和带空格的参数。 (2认同)

Sté*_*las 12

有这个getopts_long编写为 POSIX shell 函数,您可以将其嵌入脚本中。

请注意,Linux getopt(来自util-linux)在非传统模式下可以正常工作并支持长选项,但如果您需要移植到其他 Unices,则可能不适合您。

最新版本的 ksh93 ( getopts) 和 zsh ( zparseopts) 内置支持解析长选项,这可能是您的一个选项,因为它们可用于大多数 Unices(尽管默认情况下通常未安装)。

另一种选择是使用perl和它的Getopt::Long模块,这两者在当今大多数 Unices 上都应该可用,要么通过编写整个脚本,perl要么只是调用 perl 来解析选项并将提取的信息提供给 shell。就像是:

parsed_ops=$(
  perl -MGetopt::Long -le '

    @options = (
      "foo=s", "bar", "neg!"
    );

    Getopt::Long::Configure "bundling";
    $q="'\''";
    GetOptions(@options) or exit 1;
    for (map /(\w+)/, @options) {
      eval "\$o=\$opt_$_";
      $o =~ s/$q/$q\\$q$q/g;
      print "opt_$_=$q$o$q"
    }' -- "$@"
) || exit
eval "$parsed_ops"
# and then use $opt_foo, $opt_bar...
Run Code Online (Sandbox Code Playgroud)

查看perldoc Getopt::Long它可以做什么以及它与其他选项解析器的不同之处。


von*_*and 11

如果它必须可移植到一系列 Unices,则必须坚持使用 POSIX sh。和 AFAIU 在那里,您别无选择,只能手动滚动参数处理。

  • `getopts` 是一个 [POSIX 标准](https://pubs.opengroup.org/onlinepubs/7908799/xcu/getopts.html)。如果脚本也需要在没有安装 GNU bash 和工具的 Solaris 等上运行,我同意。 (2认同)

小智 5

关于这个问题的每次讨论都强调手动编写解析代码的选项 - 只有这样您才能确定功能和可移植性。我建议您不要编写可以由易于使用的开源代码生成器生成和重新生成的代码。使用Argbash,它旨在为您的问题提供明确的答案。它是一个文档齐全的代码生成器,可作为命令行应用程序在线应用程序或作为Docker 映像使用。

我建议不要使用 bash 库,其中一些库使用getopt(这使得相当不可移植),并且将巨大的不可读的 shell blob 与脚本捆绑在一起是很痛苦的。