getopts による長いオプションの処理

getopts による長いオプションの処理

オプションを解析していますがgetopts、長いオプションも処理したいと考えています。

print-args ()
{
 title="$1" ; shift
 printf "\n%s\n" "${title}: \$@:"
 for arg in "$@"; do
   (( i = i + 1 ))
   printf "%s |%s|\n" "${i}." "$arg"
 done
}

getopts_test ()
{
 aggr=()
 for arg in "$@"; do
   case $arg in
    ("--colour"|"--color")     aggr+=( "-c" ) ;;
    ("--colour="*|"--color="*) aggr+=( "-c" "${arg#*=}" ) ;;
    (*)  aggr+=( "$arg" ) ;;
   esac
 done

 print-args "print" "$@"

 eval set -- "${aggr[@]}"
 print-args "eval" "$@"

 set -- "${aggr[@]}"
 print-args "set" "$@"

 local OPTIND OPTARG
 local shortopts="C:"
 while getopts "$shortopts" arg; do
   case $arg in
    ("c") context="$OPTARG" ;;
    (*) break ;;
   esac
 done
 shift $(( OPTIND - 1 ))
}

しかし、 の使い方がset -- "${aggr[@]}"正しいかどうか疑問です。

それとも、次の( を使用eval)のほうが適切でしょうか?

eval set -- "${aggr[@]}"

以下に示すテストを実行しました。eval を使用すると、文字列「Gunga Din」が分割されますが、を使用するとset -- "${aggr[@]}"、1 つの文字列として正しく解析されます。

getopts_test -f -g 130 --colour="170 20" "Gunga Din"

print: $@:
1. |-f|
2. |-g|
3. |130|
4. |--colour=170 20|
5. |Gunga Din|

eval: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170|
6. |20|
7. |Gunga|
8. |Din|

set: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170 20|
6. |Gunga Din|

次に、非 GNU を使用する別の関数を実行しましたgetopt

getopt_test ()
{
 shortopts="Vuhv::H::w::e::n::l::C:"
 shortopts="${shortopts}bgcrmo"
 longopts="version,usage,help,verbosity::"
 longopts="${longopts},heading::,warning::,error::"
 longopts="${longopts},blu,grn,cyn,red,mgn,org"
 
 opts=$( getopt -o "$shortopts" -l "$longopts" -n "${0##*/}" -- "$@" )

 print-args "\$@:" "$@"
 print-args "opts:" "$opts"

 set -- "$opts"
 print-args "set -- \"$opts\"" "$@"

 eval set -- "$opts"
 print-args "eval set -- \"$opts\"" "$@"

}

その結果、次のようになりました

getopt_test --warning=3 "foo'bar" "Gunga Din"

$@:
1. |--warning=3|
2. |foo'bar|
3. |Gunga Din|

opts:
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|

set -- "$opts"
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|

eval set -- "$opts"
1. |--warning|
2. |3|
3. |--|
4. |foo'bar|
5. |Gunga Din|

示されているように、getopt の結果は、位置引数が再配置された単一のエントリです。これは、オプションの解析と処理のために、文字列eval set -- "$opts"内の位置引数をopts5 つのエントリに分割するために を使用する必要があることを示しています。

答え1

ここでのアイデアは、引数を前処理して、それぞれを に変更し、それ--contextを処理できるようにすることです。おそらくそれは機能するでしょうが、GNU スタイルの長いオプションは 形式の引数も受け取ることができ、ここでの構成はそれをサポートしていないことに注意してください。ユーザーは、この特定のツールが2 つの異なる引数を必要とすることを知っておく必要があります。または、前処理をより複雑にする必要があります。-Cgetopts--context=foobar--context foobar

また、 で始まるすべての引数をチェックすることもできます--。そうしないと、たとえば、誤って入力した引数がそのまま--cotnextになりgetopts、不明なオプションに関するエラーが発生します。(さらに悪いことに、間違ったオプションが有効になります。)

しかし、 の使い方がset -- "${aggr[@]}"正しいかどうか疑問です。

それとも、次の方法 (eval を使用) の方が適切でしょうか?

set -- "${aggr[@]}"配列の要素を個別の単語に拡張し、それらの単語を位置パラメータに割り当てます。配列の各要素は、変更されることなく、正確に 1 つの位置パラメータになります。

eval set -- "${aggr[@]}"は配列のすべての要素を展開し、それらをスペースで結合し、 を先頭に追加しset --て結果をシェルコマンドとして評価します。つまり、配列要素がabc def、、$(date >&2)の場合ghi'jkl、コマンドは次のようになります。

set -- abc def $(date >&2) ghi'jkl 

最終的には と がabc2defつの異なるパラメータとなり、日付が stderr に出力されますが、単一引用符だけがあると構文エラーが発生します。

シェル入力用に引用された出力を生成するように設計されたものがある場合は、を使用するのevalが適切です。


Linux を使っている場合 (移植性は気にしない)、roaima がコメントで提案したように、 の util-linux バージョンgetopt( なしs) を使用できます。長いオプションもサポートされており、使用方法を示す回答があります。getopt、getopts、または手動解析 - 短いオプションと長いオプションの両方をサポートしたい場合は何を使用すればよいですか?そしてこのSOの答えそしてまた私の答えはここにあります

ちなみに、それでgetopt、あなたはだろうを使用しますeval。コマンドとしては、配列のようなリストではなく、出力として単一の文字列のみを生成するように制限されているため、シェルの引用符を使用して問題を回避します。

答え2

組み込みの で、引数を取る短いオプションとして を追加し、実際の長いオプションを から取得することで--foo、 スタイルの長いオプションを解析できます。簡単な例:getopts-$OPTARG

while getopts :sc:-: o; do
    case $o in
    :) echo >&2 "option -$OPTARG needs an argument"; continue;;
    '?') echo >&2 "unknown option -$OPTARG"; continue;;
    -) o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; OPTARG=${OPTARG#=};;
    esac
    echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"

script -c fooこれをまたはとして使用できますscript --context=foo

長いオプションも短いオプションと同様に検証し、省略形も受け入れたい場合は、もっと複雑なものが必要です。このような貧弱なシェル スクリプトを過剰に設計するのは賢明ではありませんが、例が必要な場合は、次のようになります。

short_opts=sc:
long_opts=silent/ch/context:/check/co   # those who take an arg END with :

# override via command line for testing purposes
# if [ "$#" -ge 2 ]; then
#   short_opts=$1; long_opts=$2; shift 2
# fi

while getopts ":$short_opts-:" o; do
    case $o in
    :) echo >&2 "option -$OPTARG needs an argument" ;continue;;
    '?') echo >&2 "bad option -$OPTARG" ;continue;;
    -)  o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; lo=/$long_opts/
        case $lo in
        *"/$o"[!/:]*"/$o"[!/:]*) echo >&2 "ambiguous option --$o"; continue;;
        *"/$o"[:/]*) ;;
        *) o=$o${lo#*"/$o"}; o=${o%%[/:]*} ;;
        esac
        case $lo in
        *"/$o/"*) OPTARG= ;;
        *"/$o:/"*)
            case $OPTARG in
            '='*)   OPTARG=${OPTARG#=};;
            *)  eval "OPTARG=\$$OPTIND"
                if [ "$OPTIND" -le "$#" ] && [ "$OPTARG" != -- ]; then
                    OPTIND=$((OPTIND + 1))
                else
                    echo >&2 "option --$o needs an argument"; continue
                fi;;
            esac;;
        *) echo >&2 "unknown option --$o"; continue;;
        esac
    esac
    echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"

それから

$ ./script --context=33
OPT context=33
$ ./script --con=33
OPT context=33
$ ./script --co
OPT co=
$ ./script --context
option --context needs an argument

関連情報