Using getopts to process long and short command line options

后端 未结 30 1583
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-21 22:52

I wish to have long and short forms of command line options invoked using my shell script.

I know that getopts can be used, but like in Perl, I have not

相关标签:
30条回答
  • 2020-11-21 23:24

    There are three implementations that may be considered:

    • Bash builtin getopts. This does not support long option names with the double-dash prefix. It only supports single-character options.

    • BSD UNIX implementation of standalone getopt command (which is what MacOS uses). This does not support long options either.

    • GNU implementation of standalone getopt. GNU getopt(3) (used by the command-line getopt(1) on Linux) supports parsing long options.


    Some other answers show a solution for using the bash builtin getopts to mimic long options. That solution actually makes a short option whose character is "-". So you get "--" as the flag. Then anything following that becomes OPTARG, and you test the OPTARG with a nested case.

    This is clever, but it comes with caveats:

    • getopts can't enforce the opt spec. It can't return errors if the user supplies an invalid option. You have to do your own error-checking as you parse OPTARG.
    • OPTARG is used for the long option name, which complicates usage when your long option itself has an argument. You end up having to code that yourself as an additional case.

    So while it is possible to write more code to work around the lack of support for long options, this is a lot more work and partially defeats the purpose of using a getopt parser to simplify your code.

    0 讨论(0)
  • 2020-11-21 23:24

    Here you can find a few different approaches for complex option parsing in bash: http://mywiki.wooledge.org/ComplexOptionParsing

    I did create the following one, and I think it's a good one, because it's minimal code and both long and short options work. A long option can also have multiple arguments with this approach.

    #!/bin/bash
    # Uses bash extensions.  Not portable as written.
    
    declare -A longoptspec
    longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
    optspec=":h-:"
    while getopts "$optspec" opt; do
    while true; do
        case "${opt}" in
            -) #OPTARG is name-of-long-option or name-of-long-option=value
                if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
                then
                    opt=${OPTARG/=*/}
                    OPTARG=${OPTARG#*=}
                    ((OPTIND--))    
                else #with this --key value1 value2 format multiple arguments are possible
                    opt="$OPTARG"
                    OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
                fi
                ((OPTIND+=longoptspec[$opt]))
                continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
                ;;
            loglevel)
              loglevel=$OPTARG
                ;;
            h|help)
                echo "usage: $0 [--loglevel[=]<value>]" >&2
                exit 2
                ;;
        esac
    break; done
    done
    
    # End of file
    
    0 讨论(0)
  • 2020-11-21 23:25

    Take a look at shFlags which is a portable shell library (meaning: sh, bash, dash, ksh, zsh on Linux, Solaris, etc.).

    It makes adding new flags as simple as adding one line to your script, and it provides an auto generated usage function.

    Here is a simple Hello, world! using shFlag:

    #!/bin/sh
    
    # source shflags from current directory
    . ./shflags
    
    # define a 'name' command-line string flag
    DEFINE_string 'name' 'world' 'name to say hello to' 'n'
    
    # parse the command-line
    FLAGS "$@" || exit 1
    eval set -- "${FLAGS_ARGV}"
    
    # say hello
    echo "Hello, ${FLAGS_name}!"
    

    For OSes that have the enhanced getopt that supports long options (e.g. Linux), you can do:

    $ ./hello_world.sh --name Kate
    Hello, Kate!
    

    For the rest, you must use the short option:

    $ ./hello_world.sh -n Kate
    Hello, Kate!
    

    Adding a new flag is as simple as adding a new DEFINE_ call.

    0 讨论(0)
  • 2020-11-21 23:25

    If all your long options have unique, and matching, first characters as the short options, so for example

    ./slamm --chaos 23 --plenty test -quiet
    

    Is the same as

    ./slamm -c 23 -p test -q
    

    You can use this before getopts to rewrite $args:

    # change long options to short options
    
    for arg; do 
        [[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
        if [ "${arg:0:2}" == "--" ]; 
           then args="${args} -${arg:2:1}" 
           else args="${args} ${delim}${arg}${delim}"
        fi
    done
    
    # reset the incoming args
    eval set -- $args
    
    # proceed as usual
    while getopts ":b:la:h" OPTION; do
        .....
    

    Thanks for mtvee for the inspiration ;-)

    0 讨论(0)
  • 2020-11-21 23:25

    Th built-in OS X (BSD) getopt does not support long options, but the GNU version does: brew install gnu-getopt. Then, something similar to: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.

    0 讨论(0)
  • 2020-11-21 23:26

    Another way...

    # translate long options to short
    for arg
    do
        delim=""
        case "$arg" in
           --help) args="${args}-h ";;
           --verbose) args="${args}-v ";;
           --config) args="${args}-c ";;
           # pass through anything else
           *) [[ "${arg:0:1}" == "-" ]] || delim="\""
               args="${args}${delim}${arg}${delim} ";;
        esac
    done
    # reset the translated args
    eval set -- $args
    # now we can process with getopt
    while getopts ":hvc:" opt; do
        case $opt in
            h)  usage ;;
            v)  VERBOSE=true ;;
            c)  source $OPTARG ;;
            \?) usage ;;
            :)
            echo "option -$OPTARG requires an argument"
            usage
            ;;
        esac
    done
    
    0 讨论(0)
提交回复
热议问题