Using getopts to process long and short command line options

后端 未结 30 1582
佛祖请我去吃肉
佛祖请我去吃肉 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:17

    EasyOptions handles short and long options:

    ## Options:
    ##   --verbose, -v   Verbose mode
    ##   --logfile=NAME  Log filename
    
    source easyoptions || exit
    
    if test -n "${verbose}"; then
        echo "log file: ${logfile}"
        echo "arguments: ${arguments[@]}"
    fi
    
    0 讨论(0)
  • 2020-11-21 23:19

    An improved solution:

    # translate long options to short
    # Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after  "--" in option fields.
    for ((i=1;$#;i++)) ; do
        case "$1" in
            --)
                # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
                EndOpt=1 ;;&
            --version) ((EndOpt)) && args[$i]="$1"  || args[$i]="-V";;
            # default case : short option use the first char of the long option:
            --?*) ((EndOpt)) && args[$i]="$1"  || args[$i]="-${1:2:1}";;
            # pass through anything else:
            *) args[$i]="$1" ;;
        esac
        shift
    done
    # reset the translated args
    set -- "${args[@]}"
    
    function usage {
    echo "Usage: $0 [options] files" >&2
        exit $1
    }
    
    # now we can process with getopt
    while getopts ":hvVc:" opt; do
        case $opt in
            h)  usage ;;
            v)  VERBOSE=true ;;
            V)  echo $Version ; exit ;;
            c)  source $OPTARG ;;
            \?) echo "unrecognized option: -$opt" ; usage -1 ;;
            :)
            echo "option -$OPTARG requires an argument"
            usage -1
            ;;
        esac
    done
    
    shift $((OPTIND-1))
    [[ "$1" == "--" ]] && shift
    
    0 讨论(0)
  • 2020-11-21 23:21

    getopts "could be used" for parsing long options as long as you don't expect them to have arguments...

    Here's how to:

    $ cat > longopt
    while getopts 'e:-:' OPT; do
      case $OPT in
        e) echo echo: $OPTARG;;
        -) #long option
           case $OPTARG in
             long-option) echo long option;;
             *) echo long option: $OPTARG;;
           esac;;
      esac
    done
    
    $ bash longopt -e asd --long-option --long1 --long2 -e test
    echo: asd
    long option
    long option: long1
    long option: long2
    echo: test
    

    If you try to use OPTIND for getting a parameter for the long option, getopts will treat it as the first no optional positional parameter and will stop parsing any other parameters. In such a case you'll be better off handling it manually with a simple case statement.

    This will "always" work:

    $ cat >longopt2
    while (($#)); do
        OPT=$1
        shift
        case $OPT in
            --*) case ${OPT:2} in
                long1) echo long1 option;;
                complex) echo comples with argument $1; shift;;
            esac;;
    
            -*) case ${OPT:1} in
                a) echo short option a;;
                b) echo short option b with parameter $1; shift;;
            esac;;
        esac
    done
    
    
    $ bash longopt2 --complex abc -a --long -b test
    comples with argument abc
    short option a
    short option b with parameter test
    

    Albeit is not as flexible as getopts and you have to do much of the error checking code yourself within the case instances...

    But it is an option.

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

    Builtin getopts only parse short options (except in ksh93), but you can still add few lines of scripting to make getopts handles long options.

    Here is a part of code found in http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

      #== set short options ==#
    SCRIPT_OPTS=':fbF:B:-:h'
      #== set long options associated with short one ==#
    typeset -A ARRAY_OPTS
    ARRAY_OPTS=(
        [foo]=f
        [bar]=b
        [foobar]=F
        [barfoo]=B
        [help]=h
        [man]=h
    )
    
      #== parse options ==#
    while getopts ${SCRIPT_OPTS} OPTION ; do
        #== translate long options to short ==#
        if [[ "x$OPTION" == "x-" ]]; then
            LONG_OPTION=$OPTARG
            LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
            LONG_OPTIND=-1
            [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
            [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
            OPTION=${ARRAY_OPTS[$LONG_OPTION]}
            [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"
    
            if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
                if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                    OPTION=":" OPTARG="-$LONG_OPTION"
                else
                    OPTARG="$LONG_OPTARG";
                    if [[ $LONG_OPTIND -ne -1 ]]; then
                        [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                        shift $OPTIND
                        OPTIND=1
                    fi
                fi
            fi
        fi
    
        #== options follow by another option instead of argument ==#
        if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
            OPTARG="$OPTION" OPTION=":"
        fi
    
        #== manage options ==#
        case "$OPTION" in
            f  ) foo=1 bar=0                    ;;
            b  ) foo=0 bar=1                    ;;
            B  ) barfoo=${OPTARG}               ;;
            F  ) foobar=1 && foobar_name=${OPTARG} ;;
            h ) usagefull && exit 0 ;;
            : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
            ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
        esac
    done
    shift $((${OPTIND} - 1))
    

    Here is a test:

    # Short options test
    $ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
    foo=0 bar=1
    barfoo=6
    foobar=1 foobar_name=Hello world
    files=file1 file2
    
    # Long and short options test
    $ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
    foo=0 bar=1
    barfoo=6
    foobar=1 foobar_name=Hello
    files=file1 file2
    

    Otherwise in recent Korn Shell ksh93, getopts can naturally parse long options and even display a man page alike. (See http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options)

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

    Using getopts with short/long options and arguments


    Works with all combinations, e.g:

    • foobar -f --bar
    • foobar --foo -b
    • foobar -bf --bar --foobar
    • foobar -fbFBAshorty --bar -FB --arguments=longhorn
    • foobar -fA "text shorty" -B --arguments="text longhorn"
    • bash foobar -F --barfoo
    • sh foobar -B --foobar - ...
    • bash ./foobar -F --bar

    Some declarations for this example

    Options=$@
    Optnum=$#
    sfoo='no '
    sbar='no '
    sfoobar='no '
    sbarfoo='no '
    sarguments='no '
    sARG=empty
    lfoo='no '
    lbar='no '
    lfoobar='no '
    lbarfoo='no '
    larguments='no '
    lARG=empty
    

    How the Usage function would look

    function _usage() 
    {
      ###### U S A G E : Help and ERROR ######
      cat <<EOF
       foobar $Options
      $*
              Usage: foobar <[options]>
              Options:
                      -b   --bar            Set bar to yes    ($foo)
                      -f   --foo            Set foo to yes    ($bart)
                      -h   --help           Show this message
                      -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                      -B   --barfoo         Set barfoo to yes ($barfoo)
                      -F   --foobar         Set foobar to yes ($foobar)
    EOF
    }
    
    [ $# = 0 ] && _usage "  >>>>>>>> no options given "
    

    getops with long/short flags as well as long arguments

    while getopts ':bfh-A:BF' OPTION ; do
      case "$OPTION" in
        b  ) sbar=yes                       ;;
        f  ) sfoo=yes                       ;;
        h  ) _usage                         ;;   
        A  ) sarguments=yes;sARG="$OPTARG"  ;;
        B  ) sbarfoo=yes                    ;;
        F  ) sfoobar=yes                    ;;
        -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
             eval OPTION="\$$optind"
             OPTARG=$(echo $OPTION | cut -d'=' -f2)
             OPTION=$(echo $OPTION | cut -d'=' -f1)
             case $OPTION in
                 --foo       ) lfoo=yes                       ;;
                 --bar       ) lbar=yes                       ;;
                 --foobar    ) lfoobar=yes                    ;;
                 --barfoo    ) lbarfoo=yes                    ;;
                 --help      ) _usage                         ;;
                 --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
                 * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
             esac
           OPTIND=1
           shift
          ;;
        ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
      esac
    done
    

    Output

    ##################################################################
    echo "----------------------------------------------------------"
    echo "RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
    echo "RESULT short-bar      : $sbar                                    long-bar      : $lbar"
    echo "RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
    echo "RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
    echo "RESULT short-arguments: $sarguments  with Argument = \"$sARG\"   long-arguments: $larguments and $lARG"
    

    Combining the above into a cohesive script

    #!/bin/bash
    # foobar: getopts with short and long options AND arguments
    
    function _cleanup ()
    {
      unset -f _usage _cleanup ; return 0
    }
    
    ## Clear out nested functions on exit
    trap _cleanup INT EXIT RETURN
    
    ###### some declarations for this example ######
    Options=$@
    Optnum=$#
    sfoo='no '
    sbar='no '
    sfoobar='no '
    sbarfoo='no '
    sarguments='no '
    sARG=empty
    lfoo='no '
    lbar='no '
    lfoobar='no '
    lbarfoo='no '
    larguments='no '
    lARG=empty
    
    function _usage() 
    {
      ###### U S A G E : Help and ERROR ######
      cat <<EOF
       foobar $Options
       $*
              Usage: foobar <[options]>
              Options:
                      -b   --bar            Set bar to yes    ($foo)
                        -f   --foo            Set foo to yes    ($bart)
                          -h   --help           Show this message
                      -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                      -B   --barfoo         Set barfoo to yes ($barfoo)
                      -F   --foobar         Set foobar to yes ($foobar)
    EOF
    }
    
    [ $# = 0 ] && _usage "  >>>>>>>> no options given "
    
    ##################################################################    
    #######  "getopts" with: short options  AND  long options  #######
    #######            AND  short/long arguments               #######
    while getopts ':bfh-A:BF' OPTION ; do
      case "$OPTION" in
        b  ) sbar=yes                       ;;
        f  ) sfoo=yes                       ;;
        h  ) _usage                         ;;   
        A  ) sarguments=yes;sARG="$OPTARG"  ;;
        B  ) sbarfoo=yes                    ;;
        F  ) sfoobar=yes                    ;;
        -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
             eval OPTION="\$$optind"
             OPTARG=$(echo $OPTION | cut -d'=' -f2)
             OPTION=$(echo $OPTION | cut -d'=' -f1)
             case $OPTION in
                 --foo       ) lfoo=yes                       ;;
                 --bar       ) lbar=yes                       ;;
                 --foobar    ) lfoobar=yes                    ;;
                 --barfoo    ) lbarfoo=yes                    ;;
                 --help      ) _usage                         ;;
                   --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
                 * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
             esac
           OPTIND=1
           shift
          ;;
        ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
      esac
    done
    
    0 讨论(0)
  • 2020-11-21 23:22

    In case you don't want the getopt dependency, you can do this:

    while test $# -gt 0
    do
      case $1 in
    
      # Normal option processing
        -h | --help)
          # usage and help
          ;;
        -v | --version)
          # version info
          ;;
      # ...
    
      # Special cases
        --)
          break
          ;;
        --*)
          # error unknown (long) option $1
          ;;
        -?)
          # error unknown (short) option $1
          ;;
    
      # FUN STUFF HERE:
      # Split apart combined short options
        -*)
          split=$1
          shift
          set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
          continue
          ;;
    
      # Done with options
        *)
          break
          ;;
      esac
    
      # for testing purposes:
      echo "$1"
    
      shift
    done
    

    Of course, then you can't use long style options with one dash. And if you want to add shortened versions (e.g. --verbos instead of --verbose), then you need to add those manually.

    But if you are looking to get getopts functionality along with long options, this is a simple way to do it.

    I also put this snippet in a gist.

    0 讨论(0)
提交回复
热议问题