50
while getopts "hd:R:" arg; do
  case $arg in
    h)
      echo "usage" 
      ;;
    d)
      dir=$OPTARG
      ;;
    R)
      if [[ $OPTARG =~ ^[0-9]+$ ]];then
        level=$OPTARG
      else
        level=1
      fi
      ;;
    \?)
      echo "WRONG" >&2
      ;;
  esac
done
  • level は のパラメータを-R参照し、dir は のパラメータを参照します-d

  • 入力 ./count.sh -R 1 -d test/すると正しく動作します

  • 入力./count.sh -d test/ -R 1すると正しく動作します

  • ./count.sh -d test/ -Rしかし、入力または入力時に機能させたい./count.sh -R -d test/

-Rこれは、デフォルト値を設定し、コマンドのシーケンスをより柔軟にしたいということです。

4

13 に答える 13

26

違う。実際getoptsにはオプションの引数をサポートしています! bash の man ページから:

If  a  required  argument is not found, and getopts is not silent, 
a question mark (?) is placed in name, OPTARG is unset, and a diagnostic
message is printed.  If getopts is silent, then a colon (:) is placed in name 
and OPTARG is set to the option character found.

man ページに「silent」とある場合は、サイレント エラー レポートを意味します。これを有効にするには、optstringの最初の文字をコロンにする必要があります。

while getopts ":hd:R:" arg; do
    # ...rest of iverson's loop should work as posted 
done

Bash のgetopt--はオプション リストの終了を認識しないため-R、 が最後のオプションであり、その後に何らかのパス引数が続く場合は機能しない可能性があります。

PS: 伝統的に、getopt.cは 2 つのコロン ( ::) を使用してオプションの引数を指定します。ただし、Bash で使用されるバージョンはそうではありません。

于 2013-02-10T14:22:46.960 に答える
22

getoptsこれは実際にはサポートされていません。しかし、独自の置換を作成することは難しくありません。

while true; do
    case $1 in
      -R) level=1
            shift
            case $1 in
              *[!0-9]* | "") ;;
              *) level=$1; shift ;;
            esac ;;
        # ... Other options ...
        -*) echo "$0: Unrecognized option $1" >&2
            exit 2;;
        *) break ;;
    esac
done
于 2012-07-17T07:32:13.410 に答える
21

この回避策では、引数なし (「:」なし) で「R」を定義し、「-R」の後の引数をテストし (コマンド ラインの最後のオプションを管理)、既存の引数がダッシュで始まるかどうかをテストします。

# 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
于 2016-08-01T11:28:39.673 に答える
12

getopts はオプションの引数の処理をサポートしていません。

私が妥協した解決策は、同じオプションフラグの大文字/小文字の組み合わせを使用して、引数を取るオプションと引数を取らないオプションを区別することです。

例:

COMMAND_LINE_OPTIONS_HELP='
Command line options:
    -I          Process all the files in the default dir: '`pwd`'/input/
    -i  DIR     Process all the files in the user specified input dir
    -h          Print this help menu

Examples:
    Process all files in the default input dir
        '`basename $0`' -I

    Process all files in the user specified input dir
        '`basename $0`' -i ~/my/input/dir

'

VALID_COMMAND_LINE_OPTIONS="i:Ih"
INPUT_DIR=

while getopts $VALID_COMMAND_LINE_OPTIONS options; do
    #echo "option is " $options
    case $options in
        h)
            echo "$COMMAND_LINE_OPTIONS_HELP"
            exit $E_OPTERROR;
        ;;
        I)
            INPUT_DIR=`pwd`/input
            echo ""
            echo "***************************"
            echo "Use DEFAULT input dir : $INPUT_DIR"
            echo "***************************"
        ;;
        i)
            INPUT_DIR=$OPTARG
            echo ""
            echo "***************************"
            echo "Use USER SPECIFIED input dir : $INPUT_DIR"
            echo "***************************"
        ;;
        \?)
            echo "Usage: `basename $0` -h for help";
            echo "$COMMAND_LINE_OPTIONS_HELP"
            exit $E_OPTERROR;
        ;;
    esac
done
于 2012-07-25T20:49:06.583 に答える
0

オプションを小文字または大文字で区別することはいつでも決定できます。

ただし、私の考えは、引数を無視して2回呼び出しgetopts、1回目は引数を無視して解析し( R)、​​2回目は引数サポートを使用してそのオプションのみを解析することです( R:)。唯一のトリックはOPTIND、現在の引数へのポインターを保持するため、処理中に (index) を変更する必要があることです。

コードは次のとおりです。

#!/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

機能するシナリオのいくつかのテストを次に示します。

$ ./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

動作しないシナリオ (そのため、コードをもう少し調整する必要があります):

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

getopts使用方法の詳細については、 を参照してくださいman bash

関連項目: Bash Hackers Wiki の小さな getopts チュートリアル

于 2015-12-30T13:57:37.253 に答える
0

次のコードは、先頭のダッシュをチェックすることでこの問題を解決し、見つかった場合は OPTIND をデクリメントして、処理のためにスキップされたオプションを指すようにします。これは、ユーザーがコマンド ラインにオプションを配置する順序がわからない場合を除いて、通常は正常に機能します。オプションの引数オプションが最後で、引数を指定しない場合、getopts はエラーを出力します。

最後の引数が欠落している問題を修正するには、"$@" 配列に空文字列 "$@ " を追加するだけで、getopts がさらに別のオプション引数をむさぼり食ったことを確認できるようになります。この新しい空の引数を修正するために、処理されるすべてのオプションの合計数を保持する変数が設定されます。最後のオプションが処理されると、trim と呼ばれるヘルパー関数が呼び出され、値が使用される前に空の文字列が削除されます。

これは機能するコードではありません。プレースホルダーしかありませんが、簡単に変更できます。少し注意すれば、堅牢なシステムを構築するのに役立ちます。

#!/usr/bin/env bash 
declare  -r CHECK_FLOAT="%f"  
declare  -r CHECK_INTEGER="%i"  

 ## <arg 1> Number - Number to check
 ## <arg 2> String - Number type to check
 ## <arg 3> String - Error message
function check_number() {
  local NUMBER="${1}"
  local NUMBER_TYPE="${2}"
  local ERROR_MESG="${3}"
  local FILTERED_NUMBER=$(sed 's/[^.e0-9+\^]//g' <<< "${NUMBER}")
  local -i PASS=1
  local -i FAIL=0
    if [[ -z "${NUMBER}" ]]; then 
        echo "Empty number argument passed to check_number()." 1>&2
        echo "${ERROR_MESG}" 1>&2
        echo "${FAIL}"          
  elif [[ -z "${NUMBER_TYPE}" ]]; then 
        echo "Empty number type argument passed to check_number()." 1>&2
        echo "${ERROR_MESG}" 1>&2
        echo "${FAIL}"          
  elif [[ ! "${#NUMBER}" -eq "${#FILTERED_NUMBER}" ]]; then 
        echo "Non numeric characters found in number argument passed to check_number()." 1>&2
        echo "${ERROR_MESG}" 1>&2
        echo "${FAIL}"          
  else  
   case "${NUMBER_TYPE}" in
     "${CHECK_FLOAT}")
         if ((! $(printf "${CHECK_FLOAT}" "${NUMBER}" &>/dev/random;echo $?))); then
            echo "${PASS}"
         else
            echo "${ERROR_MESG}" 1>&2
            echo "${FAIL}"
         fi
         ;;
     "${CHECK_INTEGER}")
         if ((! $(printf "${CHECK_INTEGER}" "${NUMBER}" &>/dev/random;echo $?))); then
            echo "${PASS}"
         else
            echo "${ERROR_MESG}" 1>&2
            echo "${FAIL}"
         fi
         ;;
                      *)
         echo "Invalid number type format: ${NUMBER_TYPE} to check_number()." 1>&2
         echo "${FAIL}"
         ;;
    esac
 fi 
}

 ## Note: Number can be any printf acceptable format and includes leading quotes and quotations, 
 ##       and anything else that corresponds to the POSIX specification. 
 ##       E.g. "'1e+03" is valid POSIX float format, see http://mywiki.wooledge.org/BashFAQ/054
 ## <arg 1> Number - Number to print
 ## <arg 2> String - Number type to print
function print_number() { 
  local NUMBER="${1}" 
  local NUMBER_TYPE="${2}" 
  case "${NUMBER_TYPE}" in 
      "${CHECK_FLOAT}") 
           printf "${CHECK_FLOAT}" "${NUMBER}" || echo "Error printing Float in print_number()." 1>&2
        ;;                 
    "${CHECK_INTEGER}") 
           printf "${CHECK_INTEGER}" "${NUMBER}" || echo "Error printing Integer in print_number()." 1>&2
        ;;                 
                     *) 
        echo "Invalid number type format: ${NUMBER_TYPE} to print_number()." 1>&2
        ;;                 
   esac
} 

 ## <arg 1> String - String to trim single ending whitespace from
function trim_string() { 
 local STRING="${1}" 
 echo -En $(sed 's/ $//' <<< "${STRING}") || echo "Error in trim_string() expected a sensible string, found: ${STRING}" 1>&2
} 

 ## This a hack for getopts because getopts does not support optional
 ## arguments very intuitively. E.g. Regardless of whether the values
 ## begin with a dash, getopts presumes that anything following an
 ## option that takes an option argument is the option argument. To fix  
 ## this the index variable OPTIND is decremented so it points back to  
 ## the otherwise skipped value in the array option argument. This works
 ## except for when the missing argument is on the end of the list,
 ## in this case getopts will not have anything to gobble as an
 ## argument to the option and will want to error out. To avoid this an
 ## empty string is appended to the argument array, yet in so doing
 ## care must be taken to manage this added empty string appropriately.
 ## As a result any option that doesn't exit at the time its processed
 ## needs to be made to accept an argument, otherwise you will never
 ## know if the option will be the last option sent thus having an empty
 ## string attached and causing it to land in the default handler.
function process_options() {
local OPTIND OPTERR=0 OPTARG OPTION h d r s M R S D
local ERROR_MSG=""  
local OPTION_VAL=""
local EXIT_VALUE=0
local -i NUM_OPTIONS
let NUM_OPTIONS=${#@}+1
while getopts “:h?d:DM:R:S:s:r:” OPTION "$@";
 do
     case "$OPTION" in
         h)
             help | more
             exit 0
             ;;
         r)
             OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
             ERROR_MSG="Invalid input: Integer or floating point number required."
             if [[ -z "${OPTION_VAL}" ]]; then
               ## can set global flags here 
               :;
             elif [[ "${OPTION_VAL}" =~ ^-. ]]; then
               let OPTIND=${OPTIND}-1
               ## can set global flags here 
             elif [ "${OPTION_VAL}" = "0" ]; then
               ## can set global flags here 
               :;               
             elif (($(check_number "${OPTION_VAL}" "${CHECK_FLOAT}" "${ERROR_MSG}"))); then
               :; ## do something really useful here..               
             else
               echo "${ERROR_MSG}" 1>&2 && exit -1
             fi
             ;;
         d)
             OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
             [[  ! -z "${OPTION_VAL}" && "${OPTION_VAL}" =~ ^-. ]] && let OPTIND=${OPTIND}-1            
             DEBUGMODE=1
             set -xuo pipefail
             ;;
         s)
             OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
             if [[ ! -z "${OPTION_VAL}" && "${OPTION_VAL}" =~ ^-. ]]; then ## if you want a variable value that begins with a dash, escape it
               let OPTIND=${OPTIND}-1
             else
              GLOBAL_SCRIPT_VAR="${OPTION_VAL}"
                :; ## do more important things
             fi
             ;;
         M)  
             OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
             ERROR_MSG=$(echo "Error - Invalid input: ${OPTION_VAL}, Integer required"\
                              "retry with an appropriate option argument.")
             if [[ -z "${OPTION_VAL}" ]]; then
               echo "${ERROR_MSG}" 1>&2 && exit -1
             elif [[ "${OPTION_VAL}" =~ ^-. ]]; then
               let OPTIND=${OPTIND}-1
               echo "${ERROR_MSG}" 1>&2 && exit -1
             elif (($(check_number "${OPTION_VAL}" "${CHECK_INTEGER}" "${ERROR_MSG}"))); then
             :; ## do something useful here
             else
               echo "${ERROR_MSG}" 1>&2 && exit -1
             fi
             ;;                      
         R)  
             OPTION_VAL=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
             ERROR_MSG=$(echo "Error - Invalid option argument: ${OPTION_VAL},"\
                              "the value supplied to -R is expected to be a "\
                              "qualified path to a random character device.")            
             if [[ -z "${OPTION_VAL}" ]]; then
               echo "${ERROR_MSG}" 1>&2 && exit -1
             elif [[ "${OPTION_VAL}" =~ ^-. ]]; then
               let OPTIND=${OPTIND}-1
               echo "${ERROR_MSG}" 1>&2 && exit -1
             elif [[ -c "${OPTION_VAL}" ]]; then
               :; ## Instead of erroring do something useful here..  
             else
               echo "${ERROR_MSG}" 1>&2 && exit -1
             fi
             ;;                      
         S)  
             STATEMENT=$(((${NUM_OPTIONS}==${OPTIND})) && trim_string "${OPTARG##*=}" || echo -En "${OPTARG##*=}")
             ERROR_MSG="Error - Default text string to set cannot be empty."
             if [[ -z "${STATEMENT}" ]]; then
               ## Instead of erroring you could set a flag or do something else with your code here..  
             elif [[ "${STATEMENT}" =~ ^-. ]]; then ## if you want a statement that begins with a dash, escape it
               let OPTIND=${OPTIND}-1
               echo "${ERROR_MSG}" 1>&2 && exit -1
               echo "${ERROR_MSG}" 1>&2 && exit -1
             else
                :; ## do something even more useful here you can modify the above as well 
             fi
             ;;                      
         D)  
             ## Do something useful as long as it is an exit, it is okay to not worry about the option arguments 
             exit 0
             ;;          
         *)
             EXIT_VALUE=-1
             ;&                  
         ?)
             usage
             exit ${EXIT_VALUE}
             ;;
     esac
done
}

process_options "$@ " ## extra space, so getopts can find arguments  
于 2015-03-13T00:18:46.650 に答える
0

試す:

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

上記のコードは、まだ使用している間でも目的に合うと思いますgetoptsgetopts遭遇-R時に次の 3 行をコードに追加しました。

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

-Rが検出され、最初の引数が別の getopts パラメータのように見える場合、level はデフォルト値の に設定され、1変数$OPTINDは 1 減らされます。次にgetopts引数を取得しようとすると、スキップするのではなく、正しい引数が取得されます。


このチュートリアルの Jan Schampera のコメントからのコードに基づく同様の例を次に示します。

#!/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

OPTARG von -c がハイフンで始まるものであることがわかったら、OPTIND をリセットして getopts を再実行します (while ループを続けます)。もちろん、これは完璧ではなく、もう少し堅牢性が必要です。これはほんの一例です。

于 2013-12-03T21:15:40.073 に答える
0

二通りあると思います。

最初はcalandoaの答えです。OPTINDを使用し、サイレントモードはありません。

2 つ目は、OPTIND とサイレント モードの使用です。

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"
于 2020-11-26T08:32:44.363 に答える
0

これまでに提示されたすべてのソリューションは にコードを挿入しましcase ... in ... esacたが、私の意見では、変更されたコマンドを使用する方がはるかに自然であるため、次のgetopts関数を記述しました。

編集:

オプションの引数の型を指定できるようになりました (使用法を参照してください)。

$nextArgさらに、オプション引数に「似ている」かどうかをテストする代わりに、関数は$nextArgからの文字が含まれているかどうかをチェックするようになりました$optstring。このように、 に含まれていないオプション文字は、 ' 必須の引数$optstringと同様に、オプションの引数として使用できます。getopts

最新の変更:

  • $nextArgオプション引数であるかどうかのテストを修正しました: ダッシュで始まる
    かどうかをテストします。 このテストがないと、文字 from を含むオプションの引数は認識されません。$nextArg
    $optstring
  • regexp 型指定子が追加されました (使用法に関する情報を参照してください)。
  • 修正: 0 は、int として指定されたオプションの引数として認識されません。
  • $nextArgが int であるかどうかの単純化されたテスト。
  • 型指定子正規表現と一致するかどうかをテストするために::/.../:使用します。 このようにして、(ほぼ (*)) Perl 正規表現の全機能を利用できます。 (*): 使用情報の最後の段落を参照してください。perl$nextArg

  • 修正済み: 複数の regexp 型指定子では機能しません:非貪欲な一致が必要なため、 /コンストラクトの代わりに
    使用します。perlgrepsed

使用法:

呼び出し:getopts-plus optstring name "$@"

optstring: 通常の と同様ですが、オプション文字に::getoptsを追加することにより、オプションの引数でオプションを指定できます。

ただし、スクリプトが唯一のオプション引数としてオプションの引数を持つオプションを使用した呼び出しをサポートし、その後に非オプションの引数が続く場合、非オプションの引数はオプションの引数と見なされます。

運が良く、オプションの引数が整数であることが予想され、オプション以外の引数が文字列である場合、またはその逆の場合、整数の場合は:::iを、または:::s の場合は :::sを追加して型を指定できます。その問題を解決するための文字列。

それが当てはまらない場合は、オプション文字に::/.../を追加することで、オプションの引数に Perl 正規表現を指定できます。
Perl 正規表現の紹介については、こちらを参照してください: https://perldoc.perl.org/perlretut
注意: ATM、/.../の後にのみ正規表現として認識されます::。つまり、他の区切り文字も修飾子も使用m#...#aできません。認識された。
オプションの引数を持つオプションの後に非オプションの引数がある場合、正規表現に一致する場合にのみ、オプションの引数と見なされます。
明確にするために:::/.../引数の検証を目的としたものではなく、オプションの引数を持つオプションの引数と非オプションの引数を区別することだけを目的としています。

#!/bin/bash

# Invocation: getopts-plus optstring name "$@"\
# \
# optstring: Like normal getopts, but you may specify options with optional argument
# by appending :: to the option letter.\
# \
# However, if your script supports an invocation with an option with optional
# argument as the only option argument, followed by a non-option argument,
# the non-option argument will be considered to be the argument for the option.\
# \
# If you're lucky and the optional argument is expected to be an integer, whereas
# the non-option argument is a string or vice versa, you may specify the type by
# appending :::i for an integer or :::s for a string to solve that issue.\
# \
# If that doesn't apply, you may specify a Perl regexp for the optional arg by appending
# ::/.../ to the option letter.\
# See here for an introduction to Perl regexps: https://perldoc.perl.org/perlretut
# Please note: ATM, only /.../ will be recognised as a regexp after ::,\
# i. e. neither other delimiters, nor modifiers may be used, so e. g. m#...#a will
# not be recognised.\
# If there is a non-option argument after the option with optional argument, it will
# be considered to be the optional argument only if it matches the regexp.\
# To be clear: ::/.../ is not meant for argument validation but solely to discriminate
# between arguments for options with optional argument and non-option arguments.
function getopts-plus
{
    local optstring=$1
    local -n name=$2

    shift 2

    local optionalArgSuffixRE='::(?::[si]|/.*?/)?'
    local optionalArgTypeCaptureRE=':::([si])|::(/.*?/)'

    # If we pass 'opt' for 'name' (as I always do when using getopts) and there is
    # also a local variable 'opt', the "outer" 'opt' will always be empty.
    # I don't understand why a local variable interferes with caller's variable with
    # same name in this case; however, we can easily circumvent this.
    local opt_

    # Extract options with optional arg

    local -A isOptWithOptionalArg

    while read opt_; do
        # Using an associative array as set
        isOptWithOptionalArg[$opt_]=1
    done <<<$(perlGetCaptures "$optstring" "([a-zA-Z])$optionalArgSuffixRE")

    # Extract all option letters (used to weed out possible optional args that are option args)
    local optLetters=$(perlGetCaptures "$optstring" "([a-zA-Z])(?:$optionalArgSuffixRE|:)?")

    # Save original optstring, then remove our suffix(es)
    local optstringOrg=$optstring
    optstring=$(perl -pe "s#$optionalArgSuffixRE##g" <<<$optstring)

    getopts $optstring name "$@" || return # Return value is getopts' exit value.

    # If current option is an option with optional arg and if an arg has been provided,
    # check if that arg is not an option and if it isn't, check if that arg matches(*)
    # the specified type, if any, and if it does or no type has been specified,
    # assign it to OPTARG and inc OPTIND.
    #
    # (*) We detect an int because it's easy, but we assume a string if it's not an int
    # because detecting a string would be complicated.
    # So it sounds strange to call it a match if we know that the optional arg is specified
    # to be a string, but merely that the provided arg is not an int, but in this context,
    # "not an int" is equivalent to "string". At least I think so, but I might be wrong.

    if ((isOptWithOptionalArg[$name])) && [[ ${!OPTIND} ]]; then
        local nextArg=${!OPTIND} foundOpt=0

        # Test if $nextArg is an option arg
        if [[ $nextArg == -* ]]; then
            # Check if $nextArg contains a letter from $optLetters.
            # This way, an option not contained in $optstring can be
            # used as optional arg, as with getopts' mandatory args.

            local i

            # Start at char 1 to skip the leading dash
            for ((i = 1; i < ${#nextArg}; i++)); do
                while read opt_; do
                    [[ ${nextArg:i:1} == $opt_ ]] && foundOpt=1 && break 2
                done <<<$optLetters
            done

            ((foundOpt)) && return
        fi

        # Extract type of optional arg if specified
        local optArgType=$(perlGetCaptures "$optstringOrg" "$name(?:$optionalArgTypeCaptureRE)" '$1$2')

        local nextArgIsOptArg=0

        case $optArgType in
            /*/) # Check if $nextArg matches regexp
                perlMatch "$nextArg" "$optArgType" && nextArgIsOptArg=1
                ;;
            [si]) # Check if $nextArg is an int
                local nextArgIsInt=0

                [[ $nextArg =~ ^[0-9]+$ ]] && nextArgIsInt=1

                # Test if specified type and arg type match (see (*) above).
                # N.B.: We need command groups since && and || between commands have same precedence.
                { [[ $optArgType == i ]] && ((nextArgIsInt)) || { [[ $optArgType == s ]] && ((! nextArgIsInt)); }; } && nextArgIsOptArg=1
                ;;
            '') # No type or regexp specified => Assume $nextArg is optional arg.
                nextArgIsOptArg=1
                ;;
        esac

        if ((nextArgIsOptArg)); then
            OPTARG=$nextArg && ((OPTIND++))
        fi
    fi
}

# Uses perl to match \<string\> against \<regexp\>.\
# Returns with code 0 on a match and 1 otherwise.
function perlMatch # Args: <string> <regexp>
{
    perl -e 'q('"$1"') =~ '"$2"' and exit 0; exit 1;'
}

# Uses perl to match \<string\> against \<regexp\>
# and prints each capture on a separate line.\
# If \<regexp\> contains more than one capture group,
# you must specify the \<line format\> which is an
# arbitrary Perl string containing your desired backrefs.\
# By default, merely $1 will be printed.
function perlGetCaptures # Args: <string> <regexp> [<line format>]
{
    local lineFmt=${3:-\$1}

    # Matching repeatedly with g option gives one set of captures at a time.
    perl -e 'while (q('"$1"') =~ m#'"$2"'#g) { print(qq('"$lineFmt"') . "\n"); }'
}

必要がない場合に備えて、関数本体内にコメントがない同じスクリプト:

#!/bin/bash

# Invocation: getopts-plus optstring name "$@"\
# \
# optstring: Like normal getopts, but you may specify options with optional argument
# by appending :: to the option letter.\
# \
# However, if your script supports an invocation with an option with optional
# argument as the only option argument, followed by a non-option argument,
# the non-option argument will be considered to be the argument for the option.\
# \
# If you're lucky and the optional argument is expected to be an integer, whereas
# the non-option argument is a string or vice versa, you may specify the type by
# appending :::i for an integer or :::s for a string to solve that issue.\
# \
# If that doesn't apply, you may specify a Perl regexp for the optional arg by appending
# ::/.../ to the option letter.\
# See here for an introduction to Perl regexps: https://perldoc.perl.org/perlretut
# Please note: ATM, only /.../ will be recognised as a regexp after ::,\
# i. e. neither other delimiters, nor modifiers may be used, so e. g. m#...#a will
# not be recognised.\
# If there is a non-option argument after the option with optional argument, it will
# be considered to be the optional argument only if it matches the regexp.\
# To be clear: ::/.../ is not meant for argument validation but solely to discriminate
# between arguments for options with optional argument and non-option arguments.
function getopts-plus
{
    local optstring=$1
    local -n name=$2

    shift 2

    local optionalArgSuffixRE='::(?::[si]|/.*?/)?'
    local optionalArgTypeCaptureRE=':::([si])|::(/.*?/)'

    local opt_

    local -A isOptWithOptionalArg

    while read opt_; do
        isOptWithOptionalArg[$opt_]=1
    done <<<$(perlGetCaptures "$optstring" "([a-zA-Z])$optionalArgSuffixRE")

    local optLetters=$(perlGetCaptures "$optstring" "([a-zA-Z])(?:$optionalArgSuffixRE|:)?")

    local optstringOrg=$optstring
    optstring=$(perl -pe "s#$optionalArgSuffixRE##g" <<<$optstring)

    getopts $optstring name "$@" || return

    if ((isOptWithOptionalArg[$name])) && [[ ${!OPTIND} ]]; then
        local nextArg=${!OPTIND} foundOpt=0

        if [[ $nextArg == -* ]]; then
            local i

            for ((i = 1; i < ${#nextArg}; i++)); do
                while read opt_; do
                    [[ ${nextArg:i:1} == $opt_ ]] && foundOpt=1 && break 2
                done <<<$optLetters
            done

            ((foundOpt)) && return
        fi

        local optArgType=$(perlGetCaptures "$optstringOrg" "$name(?:$optionalArgTypeCaptureRE)" '$1$2')

        local nextArgIsOptArg=0

        case $optArgType in
            /*/)
                perlMatch "$nextArg" "$optArgType" && nextArgIsOptArg=1
                ;;
            [si])
                local nextArgIsInt=0

                [[ $nextArg =~ ^[0-9]+$ ]] && nextArgIsInt=1

                { [[ $optArgType == i ]] && ((nextArgIsInt)) || { [[ $optArgType == s ]] && ((! nextArgIsInt)); }; } && nextArgIsOptArg=1
                ;;
            '')
                nextArgIsOptArg=1
                ;;
        esac

        if ((nextArgIsOptArg)); then
            OPTARG=$nextArg && ((OPTIND++))
        fi
    fi
}

# Uses perl to match \<string\> against \<regexp\>.\
# Returns with code 0 on a match and 1 otherwise.
function perlMatch # Args: <string> <regexp>
{
    perl -e 'q('"$1"') =~ '"$2"' and exit 0; exit 1;'
}

# Uses perl to match \<string\> against \<regexp\>
# and prints each capture on a separate line.\
# If \<regexp\> contains more than one capture group,
# you must specify the \<line format\> which is an
# arbitrary Perl string containing your desired backrefs.\
# By default, merely $1 will be printed.
function perlGetCaptures # Args: <string> <regexp> [<line format>]
{
    local lineFmt=${3:-\$1}

    perl -e 'while (q('"$1"') =~ m#'"$2"'#g) { print(qq('"$lineFmt"') . "\n"); }'
}

最新バージョンを使用したいくつかのテスト:

整数として指定されたオプションの arg 型。int-gは渡されませんが、その後に非オプション文字列 arg が続きます。

$ . ./getopts-plus.sh
$ while getopts-plus 'b:c::de::f::g:::ia' opt -ab 99 -c 11 -def 55 -g "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done

opt == 'a'

OPTARG == ''

-------------------------

opt == 'b'

OPTARG == '99'

-------------------------

opt == 'c'

OPTARG == '11'

-------------------------

opt == 'd'

OPTARG == ''

-------------------------

opt == 'e'

OPTARG == ''

-------------------------

opt == 'f'

OPTARG == '55'

-------------------------

opt == 'g'

OPTARG == '' <-- Empty because "hello you" is not an int

上記と同様ですが、int arg を使用します。

$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ia' opt -ab 99 -c 11 -def 55 -g 7 "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done

opt == 'a'

OPTARG == ''

-------------------------

opt == 'b'

OPTARG == '99'

-------------------------

opt == 'c'

OPTARG == '11'

-------------------------

opt == 'd'

OPTARG == ''

-------------------------

opt == 'e'

OPTARG == ''

-------------------------

opt == 'f'

OPTARG == '55'

-------------------------

opt == 'g'

OPTARG == '7' <-- The passed int

-hregexpのオプション オプションが追加されまし/^(a|b|ab|ba)$/た。引数は渡されませんでした。

$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/a' opt -ab 99 -c 11 -def 55 -gh "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done

opt == 'a'

OPTARG == ''

-------------------------

opt == 'b'

OPTARG == '99'

-------------------------

opt == 'c'

OPTARG == '11'

-------------------------

opt == 'd'

OPTARG == ''

-------------------------

opt == 'e'

OPTARG == ''

-------------------------

opt == 'f'

OPTARG == '55'

-------------------------

opt == 'g'

OPTARG == ''

-------------------------

opt == 'h'

OPTARG == '' <-- Empty because "hello you" does not match the regexp

上記と同様ですが、正規表現に一致する引数を使用します。

$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/a' opt -ab 99 -c 11 -def 55 -gh ab "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done

opt == 'a'

OPTARG == ''

-------------------------

opt == 'b'

OPTARG == '99'

-------------------------

opt == 'c'

OPTARG == '11'

-------------------------

opt == 'd'

OPTARG == ''

-------------------------

opt == 'e'

OPTARG == ''

-------------------------

opt == 'f'

OPTARG == '55'

-------------------------

opt == 'g'

OPTARG == ''

-------------------------

opt == 'h'

OPTARG == 'ab' <-- The arg that matches the regexp

-iregexp (英数字またはアンダースコアを意味する/^\w+$/Perl トークンを使用) を使用して、別の regexp 型のオプション オプションを追加しました。引数は渡されませんでした。\w

$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/ai::/^\w+$/' opt -ab 99 -c 11 -def 55 -gh ab -i "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
[23:10:49]

opt == 'a'

OPTARG == ''

-------------------------

opt == 'b'

OPTARG == '99'

-------------------------

opt == 'c'

OPTARG == '11'

-------------------------

opt == 'd'

OPTARG == ''

-------------------------

opt == 'e'

OPTARG == ''

-------------------------

opt == 'f'

OPTARG == '55'

-------------------------

opt == 'g'

OPTARG == ''

-------------------------

opt == 'h'

OPTARG == 'ab'

-------------------------

opt == 'i'

OPTARG == '' <-- Empty because "hello you" contains a space.

上記と同様ですが、正規表現に一致する引数を使用します。

$ OPTIND=1
$ while getopts-plus 'b:c::de::f::g:::ih::/^(a|b|ab|ba)$/ai::/^\w+$/' opt -ab 99 -c 11 -def 55 -gh ab -i foo_Bar_1 "hello you"; do e opt OPTARG; echo; printf "%.0s-" $(seq 1 25); echo -e "\n"; done
[23:15:23]

opt == 'a'

OPTARG == ''

-------------------------

opt == 'b'

OPTARG == '99'

-------------------------

opt == 'c'

OPTARG == '11'

-------------------------

opt == 'd'

OPTARG == ''

-------------------------

opt == 'e'

OPTARG == ''

-------------------------

opt == 'f'

OPTARG == '55'

-------------------------

opt == 'g'

OPTARG == ''

-------------------------

opt == 'h'

OPTARG == 'ab'

-------------------------

opt == 'i'

OPTARG == 'foo_Bar_1' <-- Matched because it contains only alphanumeric chars and underscores.
于 2021-07-06T20:06:23.393 に答える