跨平台的shell脚本getopt

12

我刚刚发现getopt在跨平台方面存在问题(尤其是在FreeBSD和Linux上)。有什么最好的解决办法吗?


如果您一开始使用GNU getopt与长选项等,现在正在寻求尽可能少更改地使现有代码可移植,则可以在https://dev59.com/h3RC5IYBdhLWcg3wG9bp#37087374找到有用的信息。 - phils
4个回答

30

实际上,getopt 命令有两个版本:原始版本和 GNU 增强版本。GNU 增强版本向后兼容原始版本,因此如果您只使用原始版本的功能,则可以同时使用两个版本。

检测可用的 getopt 版本

您可以检测可用的版本并在 GNU 增强版本可用时使用增强功能,如果 GNU 增强版本不可用,则限制自己使用原始功能。增强版本有一个 -T 选项用于测试可用的版本。

getopt -T > /dev/null
if [ $? -eq 4 ]; then
    # GNU enhanced getopt is available
    set -- `getopt --long help,output:,version --options ho:v -- "$@"`
else
    # Original getopt is available
    set -- `getopt ho:v "$@"`
fi

考虑使用内置的shell命令getopts(带有"s"),因为它更具可移植性。然而,getopts不支持长选项(例如--help)。
如果你喜欢长选项,请使用getopt并使用上述测试来查看GNU增强版本的getopt是否可用。如果增强版不可用,脚本可以优雅地降级到使用原始版本的getopt(不支持长选项名称和空格支持)或使用getopts(不支持长选项名称)。
正确使用GNU增强版的getopt
使GNU增强版正确处理带有空格的参数很棘手。这是如何做到的:
ARGS=`getopt --long help,output:,verbose --options ho:v -- "$@"`
if [ $? -ne 0 ]; then
  echo "Usage error (use -h for help)" >&2
  exit 2
fi
eval set -- $ARGS

# Parameters are now sorted: options appear first, followed by --, then arguments
# e.g. entering: "foo bar" -o abc baz -v
#      produces: -o 'abc' -v -- 'foo bar' 'baz'

秘诀是在第一行中使用"$@",双引号非常重要,并在第6行对set命令进行eval处理。这样可以分别使用getopt和ARGS变量调用来检测和处理getopt引发的错误。 完整的工作示例:
PROG=`basename $0`

getopt -T > /dev/null
if [ $? -eq 4 ]; then
  # GNU enhanced getopt is available
  ARGS=`getopt --name "$PROG" --long help,output:,verbose --options ho:v -- "$@"`
else
  # Original getopt is available (no long option names, no whitespace, no sorting)
  ARGS=`getopt ho:v "$@"`
fi
if [ $? -ne 0 ]; then
  echo "$PROG: usage error (use -h for help)" >&2
  exit 2
fi
eval set -- $ARGS

while [ $# -gt 0 ]; do
    case "$1" in
        -h | --help)     HELP=yes;;
        -o | --output)   OUTFILE="$2"; shift;;
        -v | --verbose)  VERBOSE=yes;;
        --)              shift; break;; # end of options
    esac
    shift
done

if [ $# -gt 0 ]; then
  # Remaining parameters can be processed
  for ARG in "$@"; do
    echo "$PROG: argument: $ARG"
  done
fi

echo "$PROG: verbose: $VERBOSE"
echo "$PROG: output: $OUTFILE"
echo "$PROG: help: $HELP"

这个示例可以从https://gist.github.com/hoylen/6607180下载。

维基百科上关于getopts的条目中的比较表格可以比较不同的功能。


是的,getopts(带有s)是另一个选项。可移植性纯粹主义者仍然更喜欢 getopt,因为在1986年之前的古老 Bourne shell 中没有可用的 getopts,但这是一个不好的理由,因为大多数/所有现代 shell 都支持 get opts。更好的理由是使其易于利用 GNU 增强版 getopt(如果可用)。GNU 增强版 getopt 允许将操作数与选项混合使用,并支持长选项名称(这两个功能 getopts 不支持-请参见[比较表][1])。 - Hoylen
当我运行eval set -- $ARGS时,为什么会打印一个空的--?这非常令人烦恼。 - Hindol
经过我的调查,这似乎是目前我能够进行一些修改后最好的解决方案!如果getopt没有被GNU增强,我会使用getopts,以便更好地处理空格并与增强版兼容。 - Wenhao Ji

15

使用getopts(带有“s”)。

根据Bash FAQ 35

除非你使用的是 util-linux 版本,并且使用其高级模式,否则永远不要使用 getopt(1)。getopt 无法处理空参数字符串或带有嵌入式空格的参数。请忘记它曾经存在过。

POSIX shell(和其他一些shell)提供了 getopts,它是安全的替代品。


我使用助记符“S”来表示Shell,getopt“S”已经内置于BASH中。 - Felipe Alvarez
@Dennis Williamson,请更新您的答案:根据(更新后的)FAQ条目:除非它是来自util-linux的版本,并且您使用其高级模式,否则永远不要使用getopt(1)。... - Florian
@Florian:已更新。谢谢。 - Dennis Williamson

1

getopt 的基本语法是跨平台的。

getopt vi: -v -i 100 file

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接