Optional option argument with getopts

前端 未结 11 1251
while getopts \"hd:R:\" arg; do
  case $arg in
    h)
      echo \"usgae\" 
      ;;
    d)
      dir=$OPTARG
      ;;
    R)
      if [[ $OPTARG =~ ^[0-9]+$ ]];then         


        
相关标签:
11条回答
  • 2020-11-28 09:09

    I think there are two way.

    First is calandoa's answer, Using OPTIND and no silent mode.

    Second is Using OPTIND and silent mode.

    while getopts ":Rr:" name; do
        case ${name} in
            R)
                eval nextArg=\${$OPTIND}
                # check option followed by nothing or other option.
                if [[ -z ${nextArg} || $nextArg =~ ^-.* ]]; then
                    level=1
                elif [[ $nextArg =~ ^[0-9]+$ ]]; then
                    level=$nextArg
                    OPTIND=$((OPTIND + 1))
                else
                    level=1
                fi
                ;;
            r)
                # check option followed by other option.
                if [[ $OPTARG =~ ^-.* ]]; then
                    OPTIND=$((OPTIND - 1))
                    level2=2
                elif [[ $OPTARG =~ ^[0-9]+$ ]]; then
                    level2="$OPTARG"
                else
                    level2=2
                fi
                ;;
            :)
                # check no argument
                case $OPTARG in
                    r)
                        level2=2
                        ;;
                esac
        esac
    done
    
    echo "Level 1 : $level"
    echo "Level 2 : $level2"
    
    0 讨论(0)
  • 2020-11-28 09:11

    You can always decide to differentiate the option with lowercase or uppercase.

    However my idea is to call getopts twice and 1st time parse without arguments ignoring them (R) then 2nd time parse only that option with argument support (R:). The only trick is that OPTIND (index) needs to be changed during processing, as it keeps pointer to the current argument.

    Here is the code:

    #!/usr/bin/env bash
    while getopts ":hd:R" arg; do
      case $arg in
        d) # Set directory, e.g. -d /foo
          dir=$OPTARG
          ;;
        R) # Optional level value, e.g. -R 123
          OI=$OPTIND # Backup old value.
          ((OPTIND--)) # Decrease argument index, to parse -R again.
          while getopts ":R:" r; do
            case $r in
              R)
                # Check if value is in numeric format.
                if [[ $OPTARG =~ ^[0-9]+$ ]]; then
                  level=$OPTARG
                else
                  level=1
                fi
              ;;
              :)
                # Missing -R value.
                level=1
              ;;
            esac
          done
          [ -z "$level" ] && level=1 # If value not found, set to 1.
          OPTIND=$OI # Restore old value.
          ;;
        \? | h | *) # Display help.
          echo "$0 usage:" && grep " .)\ #" $0
          exit 0
          ;;
      esac
    done
    echo Dir: $dir
    echo Level: $level
    

    Here are few tests for scenarios which works:

    $ ./getopts.sh -h
    ./getopts.sh usage:
        d) # Set directory, e.g. -d /foo
        R) # Optional level value, e.g. -R 123
        \? | h | *) # Display help.
    $ ./getopts.sh -d /foo
    Dir: /foo
    Level:
    $ ./getopts.sh -d /foo -R
    Dir: /foo
    Level: 1
    $ ./getopts.sh -d /foo -R 123
    Dir: /foo
    Level: 123
    $ ./getopts.sh -d /foo -R wtf
    Dir: /foo
    Level: 1
    $ ./getopts.sh -R -d /foo
    Dir: /foo
    Level: 1
    

    Scenarios which doesn't work (so the code needs a bit of more tweaks):

    $ ./getopts.sh -R 123 -d /foo
    Dir:
    Level: 123
    

    More information about getopts usage can be found in man bash.

    See also: Small getopts tutorial at Bash Hackers Wiki

    0 讨论(0)
  • 2020-11-28 09:12

    This is actually pretty easy. Just drop the trailing colon after the R and use OPTIND

    while getopts "hRd:" opt; do
       case $opt in
          h) echo -e $USAGE && exit
          ;;
          d) DIR="$OPTARG"
          ;;
          R)       
            if [[ ${@:$OPTIND} =~ ^[0-9]+$ ]];then
              LEVEL=${@:$OPTIND}
              OPTIND=$((OPTIND+1))
            else
              LEVEL=1
            fi
          ;;
          \?) echo "Invalid option -$OPTARG" >&2
          ;;
       esac
    done
    echo $LEVEL $DIR
    

    count.sh -d test

    test

    count.sh -d test -R

    1 test

    count.sh -R -d test

    1 test

    count.sh -d test -R 2

    2 test

    count.sh -R 2 -d test

    2 test

    0 讨论(0)
  • 2020-11-28 09:12

    Try:

    while getopts "hd:R:" arg; do
      case $arg in
        h)
          echo "usage" 
        ;;
        d)
          dir=$OPTARG
        ;;
        R)
          if [[ $OPTARG =~ ^[0-9]+$ ]];then
            level=$OPTARG
          elif [[ $OPTARG =~ ^-. ]];then
            level=1
            let OPTIND=$OPTIND-1
          else
            level=1
          fi          
        ;;
        \?)
          echo "WRONG" >&2
        ;;
      esac
    done
    

    I think the above code will work for your purposes while still using getopts. I've added the following three lines to your code when getopts encounters -R:

          elif [[ $OPTARG =~ ^-. ]];then
            level=1
            let OPTIND=$OPTIND-1
    

    If -R is encountered and the first argument looks like another getopts parameter, level is set to the default value of 1, and then the $OPTIND variable is reduced by one. The next time getopts goes to grab an argument, it will grab the correct argument instead of skipping it.


    Here is similar example based on the code from Jan Schampera's comment at this tutorial:

    #!/bin/bash
    while getopts :abc: opt; do
      case $opt in
        a)
          echo "option a"
        ;;
        b)
          echo "option b"
        ;;
        c)
          echo "option c"
    
          if [[ $OPTARG = -* ]]; then
            ((OPTIND--))
            continue
          fi
    
          echo "(c) argument $OPTARG"
        ;;
        \?)
          echo "WTF!"
          exit 1
        ;;
      esac
    done
    

    When you discover that OPTARG von -c is something beginning with a hyphen, then reset OPTIND and re-run getopts (continue the while loop). Oh, of course, this isn't perfect and needs some more robustness. It's just an example.

    0 讨论(0)
  • 2020-11-28 09:16

    This workaround defines 'R' with no argument (no ':'), tests for any argument after the '-R' (manage last option on the command line) and tests if an existing argument starts with a dash.

    # No : after R
    while getopts "hd:R" arg; do
      case $arg in
      (...)
      R)
        # Check next positional parameter
        eval nextopt=\${$OPTIND}
        # existing or starting with dash?
        if [[ -n $nextopt && $nextopt != -* ]] ; then
          OPTIND=$((OPTIND + 1))
          level=$nextopt
        else
          level=1
        fi
        ;;
      (...)
      esac
    done
    
    0 讨论(0)
提交回复
热议问题