How can I use long options with the Bash getopts builtin?

前端 未结 9 1172
没有蜡笔的小新
没有蜡笔的小新 2020-12-24 11:25

I am trying to parse a -temp option with Bash getopts. I\'m calling my script like this:

./myscript -temp /foo/bar/someFile

He

相关标签:
9条回答
  • 2020-12-24 11:37

    It's true that builtin bash getopts only parse short options, 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 options ==#
    SCRIPT_OPTS=':fbF:B:-:h'
    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)

    Michel VONGVILAY.

    0 讨论(0)
  • 2020-12-24 11:41

    As other people explained, getopts doesn't parse long options. You can use getopt, but it's not portable (and it is broken on some platform...)

    As a workaround, you can implement a shell loop. Here an example that transforms long options to short ones before using the standard getopts command (it's simpler in my opinion):

    # Transform long options to short ones
    for arg in "$@"; do
      shift
      case "$arg" in
        "--help") set -- "$@" "-h" ;;
        "--rest") set -- "$@" "-r" ;;
        "--ws")   set -- "$@" "-w" ;;
        *)        set -- "$@" "$arg"
      esac
    done
    
    # Default behavior
    rest=false; ws=false
    
    # Parse short options
    OPTIND=1
    while getopts "hrw" opt
    do
      case "$opt" in
        "h") print_usage; exit 0 ;;
        "r") rest=true ;;
        "w") ws=true ;;
        "?") print_usage >&2; exit 1 ;;
      esac
    done
    shift $(expr $OPTIND - 1) # remove options from positional parameters
    
    0 讨论(0)
  • 2020-12-24 11:42

    Although this question was posted over 2 years ago, I found myself needing support for XFree86-style long options too; and I also wanted to take what I could from getopts. Consider the GCC switch -rdynamic. I mark r as the flag letter, and expect dynamic within $OPTARG...but, I want to reject -r dynamic, while accepting other options following r.

    The idea I've put below builds on the observation that $OPTIND will be one larger than otherwise if space (a gap) follows the flag. So, I define a bash variable to hold the previous value of $OPTIND, called $PREVOPTIND, and update it at the end of the while loop. If $OPTIND is 1 greater than $PREVOPTIND, we have no gap (i.e. -rdynamic); and $GAP is set to false. If instead $OPTIND is 2 greater than $PREVOPTIND, we do have a gap (e.g. -r dynamic), and $GAP is set to true.

    usage() { echo usage: error from $1; exit -1; }
    
    OPTIND=1
    PREVOPTIND=$OPTIND
    while getopts "t:s:o:" option; do
      GAP=$((OPTIND-(PREVOPTIND+1)))
      case $option in
        t) case "${OPTARG}" in
             emp)                  # i.e. -temp
               ((GAP)) && usage "-${option} and ${OPTARG}"
               TMPDIR="$OPTARG"
               ;;
             *)
               true
               ;;
           esac
           ;;
        s) case "${OPTARG}" in
             hots)                 # i.e. -shots
               ((GAP)) && usage
               NUMSHOTS="$OPTARG"
               ;;
             *) usage "-${option} and ${OPTARG}" ;;
           esac
           ;;
        o) OUTFILE="$OPTARG" ;;
        *) usage "-${option} and ${OPTARG}" ;;
      esac
      PREVOPTIND=$OPTIND
    done
    shift $(($OPTIND - 1))
    
    0 讨论(0)
  • 2020-12-24 11:44

    A simple DIY if you get trouble to use the built-in getopts:

    Use:

    $ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
    a1 = "a1"
    a2 = "a 2"
    a3 = "TRUE"
    a4 = ""
    a5 = "a5"
    a6 = "a 6"
    a7 = ""
    

    Script:

    #!/bin/bash
    
    function main() {
        ARGS=`getArgs "$@"`
    
        a1=`echo "$ARGS" | getNamedArg a1`
        a2=`echo "$ARGS" | getNamedArg a2`
        a3=`echo "$ARGS" | getNamedArg a3`
        a4=`echo "$ARGS" | getNamedArg a4`
        a5=`echo "$ARGS" | getNamedArg a5`
        a6=`echo "$ARGS" | getNamedArg a6`
        a7=`echo "$ARGS" | getNamedArg a7`
    
        echo "a1 = \"$a1\""
        echo "a2 = \"$a2\""
        echo "a3 = \"$a3\""
        echo "a4 = \"$a4\""
        echo "a5 = \"$a5\""
        echo "a6 = \"$a6\""
        echo "a7 = \"$a7\""
    
        exit 0
    }
    
    
    function getArgs() {
        for arg in "$@"; do
            echo "$arg"
        done
    }
    
    
    function getNamedArg() {
        ARG_NAME=$1
    
        sed --regexp-extended --quiet --expression="
            s/^--$ARG_NAME=(.*)\$/\1/p  # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
            /^--$ARG_NAME\$/ {          # Get arguments in format '--arg value' ou '--arg'
                n                       # - [n]ext, because in this format, if value exists, it will be the next argument
                /^--/! p                # - If next doesn't starts with '--', it is the value of the actual argument
                /^--/ {                 # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
                    # Then just repla[c]ed by TRUE
                    c TRUE
                }
            }
        "
    }
    
    
    main "$@"
    
    0 讨论(0)
  • 2020-12-24 11:46

    You can't use the getopts Bash builtin for long options--at least, not without having to build your own parsing functions. You should take a look at the /usr/bin/getopt binary instead (provided on my system by the util-linux package; your mileage may vary).

    See getopt(1) for specific invocation options.

    0 讨论(0)
  • 2020-12-24 11:47

    thanks to @mcoolive .

    I was able to use your $@ idea to convert whole word and long options to single letter options. Wanted to note to anyone using this idea that I also had to include shift $(expr $OPTIND - 1) prior to running the arguments through the getopts loop.

    Totally different purpose but this is working well.

    # convert long word options to short word for ease of use and portability
    
    for argu in "$@"; do
      shift
      #echo "curr arg = $1"
      case "$argu" in
    "-start"|"--start")
                       # param=param because no arg is required
                       set -- "$@" "-s"
                       ;;
    "-pb"|"--pb"|"-personalbrokers"|"--personalbrokers")
                       # pb +arg required
                       set -- "$@" "-p $1"; #echo "arg=$argu"
                       ;;
    "-stop"|"--stop")
                       # param=param because no arg is required 
                       set -- "$@" "-S" 
                       ;;
                    #  the catch all option here removes all - symbols from an
                    #  argument. if an option is attempted to be passed that is
                    #  invalid, getopts knows what to do...
                   *)  [[ $(echo $argu | grep -E "^-") ]] && set -- "$@" "${argu//-/}" || echo "no - symbol. not touching $argu" &>/dev/null
                       ;;
    esac
    done
    
    #echo -e "\n final option conversions = $@\n"
    # remove options from positional parameters for getopts parsing
    shift $(expr $OPTIND - 1)
    
    declare -i runscript=0
    # only p requires an argument hence the p:
     while getopts "sSp:" param; do
    [[ "$param" == "p" ]] && [[ $(echo $OPTARG | grep -E "^-") ]] && funcUsage "order" 
    #echo $param
    #echo "OPTIND=$OPTIND"
    case $param in
    s)
           OPTARG=${OPTARG/ /}
           getoptsRan=1
           echo "$param was passed and this is it's arg $OPTARG"
           arg0=start
           ;;
     p)
           OPTARG=${OPTARG/ /}
           getoptsRan=1
           echo "$param was passed and this is it's arg $OPTARG"
           [[ "$OPTARG" == "all" ]] && echo -e "argument \"$OPTARG\" accepted. continuing." && (( runscript += 1 )) || usage="open"
           [[ $( echo $pbString | grep -w "$OPTARG" ) ]] && echo -e "pb $OPTARG was validated. continuing.\n" && (( runscript += 1 )) || usage="personal"
           [[ "$runscript" -lt "1" ]] && funcUsage "$usage" "$OPTARG"
           arg0=start
           ;;
    S)
           OPTARG=${OPTARG/ /}
           getoptsRan=1
           echo "$param was passed and this is it's arg $OPTARG"
           arg0=stop
           ;;
    *)
           getoptsRan=1
           funcUsage
           echo -e "Invalid argument\n"
           ;;
    esac
    done
    funcBuildExcludes "$@"
    shift $((OPTIND-1))
    
    0 讨论(0)
提交回复
热议问题