我希望使用shell脚本调用长形式和短形式的命令行选项。

我知道可以使用getopts,但是像在Perl中一样,我不能用shell做同样的事情。

关于如何做到这一点的任何想法,这样我就可以使用如下选项:

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

在上面,这两个命令对我的shell意味着同样的事情,但使用getopts,我还不能实现这些?


当前回答

公认的答案很好地指出了bash内置getopts的所有缺点。答案以:

因此,虽然可以编写更多的代码来解决长选项支持不足的问题,但工作量要大得多,并且在一定程度上违背了使用getopt解析器来简化代码的目的。

尽管我原则上同意这种说法,但我觉得我们在各种脚本中实现这个特性的次数证明,我们应该花一些精力来创建一个“标准化”的、经过良好测试的解决方案。

因此,我通过在纯bash中实现getopts_long“升级”了内置在getopts中的bash,没有外部依赖。该函数的使用与内置的getopts 100%兼容。

通过在脚本中包含getopts_long(在GitHub上托管),可以简单地实现原始问题的答案:

source "${PATH_TO}/getopts_long.bash"

while getopts_long ':c: copyfile:' OPTKEY; do
    case ${OPTKEY} in
        'c'|'copyfile')
            echo 'file supplied -- ${OPTARG}'
            ;;
        '?')
            echo "INVALID OPTION -- ${OPTARG}" >&2
            exit 1
            ;;
        ':')
            echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
            exit 1
            ;;
        *)
            echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
            exit 1
            ;;
    esac
done

shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift

其他回答

内置的getopts不能做到这一点。有一个外部的getopt(1)程序可以做到这一点,但是您只能在Linux上从util-linux包中获得它。它附带一个示例脚本getop -parse.bash。

还有一个getopts_long被写成shell函数。

内置OS X (BSD) getopt不支持长选项,但GNU版本支持:brew install GNU -getopt。然后,类似于:cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt。

我还没有足够的代表来评论或投票他的解决方案,但他的答案对我来说非常有效。我遇到的唯一问题是参数以单引号结尾(所以我把它们去掉了)。

我还添加了一些示例用法和帮助文本。我将在这里附上我略微扩展的版本:

#!/bin/bash

# getopt example
# from: https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag=true ;;
    -b|--blong) bflag=true ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    -h|--help|-\?) echo -e $HELP_TEXT; exit;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done

Bash内置的getopts函数可以通过在optspec中放入破折号和冒号来解析长选项:

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

拷贝到当前工作目录中的可执行文件name=getopts_test.sh后,可以生成如下输出

$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

显然,getopts既不执行OPTERR检查,也不对长选项执行选项参数解析。上面的脚本片段展示了如何手动完成这一工作。基本原理也适用于Debian Almquist shell(“破折号”)。注意特殊情况:

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

请注意,正如http://mywiki.wooledge.org/BashFAQ上的GreyCat指出的那样,这个技巧利用了shell的非标准行为,允许选项参数(即“-ffilename”中的文件名)连接到选项(如“-ffilename”)。POSIX标准规定它们之间必须有一个空格,在"——longoption"的情况下,空格将终止选项解析并将所有长选项转换为非选项参数。

hm.

对纯bash选项不太满意。为什么不使用perl来得到你想要的。直接解析$*数组,并自动命名您的选项。

简单的帮助脚本:

#!/usr/bin/perl
use Getopt::Long;

my $optstring = shift;

my @opts = split(m#,#, $optstring);

my %opt;
GetOptions(\%opt, @opts);

print "set -- " . join(' ', map("'$_'", @ARGV)) . ";";
my $xx;

my $key;
foreach $key (keys(%opt))
{
    print "export $key='$opt{$key}'; ";
}

然后你可以在你的脚本中使用一行,例如:

#!/bin/bash

eval `getopts.pl reuse:s,long_opt:s,hello $*`;

echo "HELLO: $hello"
echo "LONG_OPT: $long_opt"
echo "REUSE: $reuse"

echo $*

/tmp/script.sh hello——reuse me——long_opt whatever_you_want_except_spaces——hello 1 2 3

你好:1 LONG_OPT: whatever_you_want_except空格 重用:我

1, 2, 3

这里唯一需要注意的是空格不适用。但它避免了bash相当复杂的循环语法,适用于长参数,自动将它们命名为变量并自动调整$*的大小,因此99%的时间都是有效的。