getoptsを使用して長いコマンドラインオプションと短いコマンドラインオプションを処理する


410

シェルスクリプトを使用して呼び出されるコマンドラインオプションの長い形式と短い形式が必要です。

私はそれgetoptsが使用できることを知っていますが、Perlのように、私はシェルで同じことをすることができませんでした。

これをどのように行うことができるかについてのアイデアは、次のようなオプションを使用できるようにします。

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

上記で、両方のコマンドは私のシェルにとって同じことを意味しますが、を使用してgetopts、これらを実装できませんでしたか?


2
私見、受け入れられた答えは最良のものではありません。@Arvid Requateが示すように、getoptsを使用して「-」と「-」の両方の引数を処理する方法は示していません。私は同様の概念を使用して別の回答を挿入していますが、必要な引数に値を挿入することを「忘れる」というユーザーエラーも処理します。重要なポイント:getoptsを機能させることができます。クロスプラットフォームの移植性が必要な場合は、代わりに「getopt」を使用しないでください。また、getoptsはシェルのPOSIX標準の一部であるため、移植可能である可能性があります。
pauljohn32 2017年

回答:


304

考えられる実装は3つあります。

  • Bashビルトインgetopts。これは、二重ダッシュの接頭辞を持つ長いオプション名をサポートしていません。1文字のオプションのみをサポートします。

  • スタンドアロンgetoptコマンドのBSD UNIX実装(MacOSが使用するもの)。これは長いオプションもサポートしていません。

  • スタンドアロンのGNU実装getopt。GNU getopt(3)getopt(1)Linuxのコマンドラインで使用)は、長いオプションの解析をサポートしています。


他のいくつかの回答は、bashビルトインgetoptsを使用して長いオプションを模倣するソリューションを示しています。そのソリューションは、実際には文字が「-」である短いオプションを作成します。したがって、フラグとして「-」を取得します。次に、それ以降はOPTARGになり、ネストされたでOPTARGをテストしますcase

これは賢い方法ですが、注意事項があります。

  • getoptsopt仕様を適用できません。ユーザーが無効なオプションを指定した場合、エラーを返すことはできません。OPTARGを解析するときに、独自のエラーチェックを行う必要があります。
  • OPTARGは長いオプション名に使用されます。これは、長いオプション自体に引数がある場合の使用を複雑にします。追加のケースとして、自分でコーディングする必要があります。

したがって、長いオプションのサポートの欠如を回避するためにさらにコードを書くことは可能ですが、これははるかに多くの作業であり、コードを簡略化するgetoptパーサーを使用する目的を部分的に無効にします。


18
そう。クロスプラットフォームのポータブルソリューションとは何ですか?
troelskn

6
GNU Getoptが唯一の選択のようです。Macでは、macportsからGNU getoptをインストールします。Windowsでは、CygwinでGNU getoptをインストールします。
ビルカーウィン、

2
どうやら、ksh getopts 長いオプション処理できます。
Tgr

1
@Bill +1。ただし、Macでソース(software.frodo.looijaard.name/getopt)からgetoptをビルドするのもかなり簡単です。「getopt -T; echo $?」を使用して、スクリプト内からシステムにインストールされているgetoptのバージョンを確認することもできます。
チャイナサー

8
@Bill Karwin:「bash getoptsビルトインは、二重ダッシュの接頭辞を持つ長いオプション名をサポートしていません。」ただし、getoptsは長いオプションをサポートするように作成できます。以下のstackoverflow.com/a/7680682/915044を参照してください。
TomRoche 2014

306

getoptgetoptsは別の獣であり、人々は彼らが何をするかについて少し誤解しているようです。 getoptsは、bashコマンドラインオプションをループで処理し、見つかった各オプションと値を組み込み変数に順に割り当てる組み込みコマンドです。これにより、さらに処理することができます。 getoptただし、は外部ユーティリティプログラムであり、実際、たとえばbash getopts、Perl Getoptモジュール、Python optparse/ argparseモジュールのようにオプションを処理しません。そのすべてgetoptすなわち、それはそれらを処理するシェルスクリプトのために簡単だそうことを、より標準的な形式に変換-んは、渡されたオプションは、正規化しています。たとえば、アプリケーションはgetopt次のものを変換する場合があります。

myscript -ab infile.txt -ooutfile.txt

これに:

myscript -a -b -o outfile.txt infile.txt

実際の処理は自分で行う必要があります。getoptオプションの指定方法にさまざまな制限を加える場合は、まったく使用する必要はありません。

  • 引数ごとに1つのオプションのみを配置します。
  • すべてのオプションは、位置パラメータ(つまり、非オプション引数)の前に配置されます。
  • 値を持つオプション(-o上記など)の場合、値は(スペースの後の)個別の引数として渡す必要があります。

getopt代わりになぜ使用するのgetoptsですか?基本的な理由は、GNUだけgetoptが長い名前のコマンドラインオプションをサポートしていることです。1getoptLinuxでは、GNU がデフォルトです。MacOS XおよびFreeBSDには、基本的であまり役に立たないが付属していますgetoptが、GNUバージョンをインストールできます。以下を参照してください。)

たとえば、次のgetopt名前の私のスクリプトからのGNUの使用例はjavawrap次のとおりです。

# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

これにより--verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"、類似または類似のオプションを指定できます。の呼び出しの効果は、getoptオプションを正規化して、--verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"より簡単に処理できるようにすることです。前後の引用符は"$1""$2"スペースを含む引数が適切に処理されるようにするために重要です。

最初の9行(行全体)を削除してeval set、コードは機能します!ただし、コードでは、どのような種類のオプションが受け入れられるかが非常に厳しくなります。特に、上記の「正規」の形式ですべてのオプションを指定する必要があります。getoptただし、を使用すると、1文字のオプションをグループ化し--file foo.txtたり、長いオプションのより明確な形式を使用したり、or --file=foo.txtスタイルを使用したり、or スタイルを使用し-m 4096たり-m4096、オプションと非オプションを任意の順序で組み合わせたりすることができます。 getoptまた、認識されないオプションやあいまいなオプションが見つかった場合にもエラーメッセージを出力します。

:実際には、基本とGNUの2つの完全に異なるバージョンがありgetopt、異なる機能と異なる呼び出し規約があります。2 Basic はかなり壊れています:長いオプションを処理しないだけでなく、引数または空の引数内の埋め込みスペースも処理できませんが、これは正しく行われます。上記のコードは基本的に動作しません。LinuxにはデフォルトでGNU がインストールされていますが、Mac OS XとFreeBSDでは個別にインストールする必要があります。Mac OS Xでは、MacPorts(http://www.macports.org)をインストールしてから、GNU (通常はに)をインストールし、それがシェルパスの先にあることを確認します。getoptgetoptgetoptgetoptsgetoptgetoptsudo port install getoptgetopt/opt/local/bin/opt/local/bin/usr/bin。FreeBSDにをインストールしmisc/getoptます。

独自のプログラムのサンプルコードを変更するためのクイックガイド:最初の数行のうち、すべてが「ボイラープレート」であり、を呼び出す行を除いて、同じままである必要がありますgetopt。後-nにプログラム名を変更し、後に短いオプションを指定し-o、後に長いオプションを指定する必要があります--long。値を取るオプションの後にコロンを置きます。

最後に、のset代わりにを含むコードが表示eval setされた場合、それはBSD用に書かれていgetoptます。eval setスタイルを使用するように変更する必要があります。これはgetopt、両方のバージョンので正常に機能しますが、プレーンsetはGNUでは正しく機能しませんgetopt

1実際にはgetoptsksh93は長い名前のオプションをサポートしていますが、このシェルはほど頻繁には使用されませんbash。ではzsh、を使用zparseoptsしてこの機能を取得します。

2技術的には、「GNU getopt」は誤称です。このバージョンは、GNUプロジェクトではなくLinux用に実際に作成されました。ただし、これはすべてのGNU規則に準拠しており、「GNU getopt」という用語が一般に使用されています(FreeBSDなど)。


3
これは非常に役に立ちました。getoptを使用してオプションをチェックし、非常に単純なループでそれらのオプションを処理するという考えは、bashスクリプトに長いスタイルのオプションを追加したいときに非常にうまくいきました。ありがとう。
ianmjones

2
getoptLinux上ではない GNUユーティリティと伝統がgetoptBSDからではなく、AT&T UNIXから最初に付属していません。ksh93 getopts(AT&Tからも)は、GNUスタイルの長いオプションをサポートしています。
ステファンChazelas 2013年

@StephaneChazelas-コメントを反映するように編集されました。このバージョンはGNUの規則に従っており、一般にGNUプログラムのように機能するため(たとえば、を利用してPOSIXLY_CORRECT)、このバージョンは誤ってこのバージョンが存在することを誤って示唆していますLinux。
アーバンバガボンド2013年

1
これはutil-linuxパッケージに由来するため、そのソフトウェアのバンドルはLinux専用であるため、これはLinuxのみです(getopt他のUnicesに簡単に移植できますが、他の多くのソフトウェアutil-linuxはLinux固有です)。GNU getopt(3)を使用するすべての非GNUプログラムは、を理解してい$POSIX_CORRECTます。たとえば、それaplayがGNUだけだとは言わないでしょう。FreeBSDがGNU getoptに言及しているとき、それらはGNU getopt(3)C APIを意味していると思います。
ステファンChazelas 2013年

@StephaneChazelas-FreeBSDには、「ビルドの依存関係:GNU getoptをインストールしてください」というエラーメッセージがあり、getoptgetopt(3)ではなく、util を明確に参照しています。
Urban Vagabond 2013年

202

Bash組み込み関数getoptsを使用して、optspecにダッシュ文字の後にコロンを続けて配置することにより、長いオプションを解析できます。

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

現在の作業ディレクトリの実行可能ファイルname = getopts_test.shにコピーした後、次のような出力を生成できます。

$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

明らかに、getopts OPTERRは長いオプションのチェックもオプション引数の解析も行いません。上記のスクリプトフラグメントは、これを手動で行う方法を示しています。基本原則は、Debian Almquistシェル(「ダッシュ」)でも機能します。特別なケースに注意してください:

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

http://mywiki.wooledge.org/BashFAQにある GreyCatが指摘しているように、このトリックは、オプション引数(つまり、「-f filename」のファイル名)を許可するシェルの非標準の動作を悪用することに注意してください。オプションに連結されます(「-ffilename」のように)。POSIX「 - longoptionは、」オプションの解析を終了させると、非オプションの引数にすべてのlongoptionsを回す標準は、の場合には、それらの間のスペース、存在しなければならないと言います。


2
1つの質問:!in のセマンティクスは何val="${!OPTIND}ですか?
TomRoche 2014

2
@TomRocheそれは間接置換です:unix.stackexchange.com/a/41293/84316
ecbrodie

2
@ecbrodie:1つの引数ではなく、2つの引数が実際に処理されたためです。最初の引数は「loglevel」という単語で、次の引数はその引数の引数です。一方、getopts自動的にOPTIND1 だけインクリメントしますが、この場合は2ずつインクリメントする必要があるため、手動で1 getoptsずつインクリメントしてから、自動的に1ずつインクリメントします。
ビクターザマニアン2014年

3
ここではbashの均衡に取り組んでいるため、裸の変数名は算術式内で使用できますが、$必要ありません。OPTIND=$(( $OPTIND + 1 ))ちょうどすることができますOPTIND=$(( OPTIND + 1 ))。さらに興味深いことに、算術式内で変数を割り当てたり増やしたりすることもできます。そのため: $(( ++OPTIND ))、さらにに短縮したり、常に正になる(( ++OPTIND ))ことを考慮したりすることもできます。++OPTINDそのため、-eオプションを使用してシェル実行を実行しません。:-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
2016

3
なぜ--very-bad警告を出さないのですか?
トム・ヘイル

148

組み込みgetoptsコマンドはまだAFAIKであり、1文字のオプションのみに制限されています。

外部プログラムが存在する(または使用されていた) getopt解析しやすいようにオプションのセットを再編成。長いオプションを処理するためにその設計を適応させることもできます。使用例:

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
    case "$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

getoptlongコマンドで同様のスキームを使用できます。

外部getoptプログラムの根本的な弱点は、スペースを含む引数を処理し、それらのスペースを正確に保持することが難しいことです。これが組み込みgetoptsが優れている理由ですが、1文字のオプションしか処理しないという制限があります。


11
getoptは、GNUバージョン(呼び出し規約が異なる)を除いて、根本的に壊れています。使用しないでください。代わりに** getoptsを使用してくださいbash-hackers.org/wiki/doku.php/howto/getopts_tutorial
hendry

9
@hendry-あなた自身のリンクから:「getoptsはGNUスタイルの長いオプション(--myoption)またはXF86スタイルの長いオプション(-myoption)を解析できないことに注意してください!」
トム・オージェ

1
ジョナサンeval set-GNU getopt(Linuxのデフォルト)でも正しく動作し、スペースが正しく処理されるように、引用符で使用するように例を書き直す必要があります(以下の私の回答を参照)。
アーバンバガボンド

@UrbanVagabond:どうしてそんなことをする必要があるのか​​よくわかりません。質問のタグはLinuxではなくUnixです。伝統的なメカニズムを意図的に示していますが、引数の空白などの問題があります。必要に応じて、最新のLinux固有のバージョンをデモンストレーションできます。答えはそれです。(の使用がいることを私のノート、ここかしこに、${1+"$@"}趣のあるおよびLinux上で見つけるだろう任意のシェルを備えたモダンな貝殻や、具体的に必要な何があるかと対立している参照してください。$を使う1:+「$ @」}で/ binに/ shの Aについてその表記法についての議論。)
ジョナサン・レフラー、2012

eval setGNUとBSDの両方で正しいことを行うため、これを行う必要がありますがgetopt、plain setはBSD でのみ正しいことを行いgetoptます。したがってeval set、これを行う習慣を人々に奨励するために使用することもできます。ところで、私はそれ${1+"$@"}がもう必要ないことを知りませんでした。私はMac OS XとLinuxの両方で機能するものを書かなければなりません-それらの2つの間でそれらは多くの移植性を強制します。私はちょうどチェックし、"$@"実際のすべてに正しいことをするのかshbashksh、およびzshMac OS Xの下。確かにLinuxの下でも。
アーバンバガボンド

78

長いオプションで実際にgetoptを使用する例を次に示します。

aflag=no
bflag=no
cargument=none

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

1
eval set引用符で使用するように例を書き直して(下記の私の回答を参照)、GNU getopt(Linuxのデフォルト)でも正しく動作し、スペースが正しく処理されるようにする必要があります。
アーバンバガボンド

2
これはgetopt問題についてgetoptsですが、これを使用しています。
Niklas Berglund

1
あり(--(-*かつ(*有効なパターン?彼らはどのように異なっている---**
Maëlan

1
@Maëlan–先頭の左括弧はオプションであるため、スタンザ(--)と同じです。省略可能なインデントとオプションの先行括弧の一貫性のない使用を見るのは奇妙ですが、答えの現在のコードは私には有効に見えます。--)case
Adam Katz

59

長いオプションは、標準オプションで「オプション」のgetopts「引数」として解析できます-

これは移植可能なネイティブPOSIXシェルです。外部プログラムやバシズムは必要ありません。

引数としてこのガイドを実装ロングオプション-オプションが、そう--alphaで見ているgetoptsよう-引数とalphaして--bravo=fooと見られている-引数を持ちますbravo=foo。真の引数は、単純な置換で取得できます${OPTARG#*=}

この例では、-band -c(およびその長い形式、--bravoand --charlie)には必須の引数があります。長いオプションの引数は等号の後にあります。たとえば--bravo=foo、長いオプションのスペース区切り文字は実装が困難です。以下を参照してください。

これはgetopts組み込みを使用しているため、このソリューションはcmd --bravo=foo -ac FILE(オプション-aを組み合わせて-c、長いオプションを標準オプションとインターリーブする)などの使用法をサポートしますが、他のほとんどの回答は苦労するか、失敗します。

die() { echo "$*" >&2; exit 2; }  # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }

while getopts ab:c:-: OPT; do
  # support long options: https://stackoverflow.com/a/28466267/519360
  if [ "$OPT" = "-" ]; then   # long option: reformulate OPT and OPTARG
    OPT="${OPTARG%%=*}"       # extract long option name
    OPTARG="${OPTARG#$OPT}"   # extract long option argument (may be empty)
    OPTARG="${OPTARG#=}"      # if long option argument, remove assigning `=`
  fi
  case "$OPT" in
    a | alpha )    alpha=true ;;
    b | bravo )    needs_arg; bravo="$OPTARG" ;;
    c | charlie )  needs_arg; charlie="$OPTARG" ;;
    ??* )          die "Illegal option --$OPT" ;;  # bad long option
    \? )           exit 2 ;;  # bad short option (error reported via getopts)
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

オプションがダッシュ(-)の場合は、長いオプションです。getopts実際の長いオプションを解析されています$OPTARG例えば、--bravo=foo元々のセットをOPT='-'してOPTARG='bravo=foo'ifスタンザセット$OPTの内容$OPTARG(最初の等号の前にbravo我々の例では)、その後の最初からその除去$OPTARG(得ない=foo何がある場合は、このステップ、または空の文字列で=)。最後に、引数の先頭を取り除き=ます。この時点で、$OPTは短いオプション(1文字)または長いオプション(2+文字)のいずれかです。

caseその後、どちらか短いか長いオプションと一致します。短いオプションの場合、getoptsオプションと欠落している引数について自動的に文句を言うのでneeds_arg$OPTARG空の場合に致命的に終了する関数を使用して手動でそれらを複製する必要があります。??*条件は、残りの長いオプションと一致します(?単一の文字と一致し*、マッチゼロ以上のように??*、私たちが終了する前に、「不正なオプション」エラーを発行することができ、2+文字にマッチします)。

(すべて大文字の変数名に関する注意:一般に、アドバイスはすべて大文字の変数をシステムで使用するために予約することです。私は$OPTそれをと一致させるためにすべて大文字として保持しています$OPTARGが、これはその規則に違反します。これはシステムが行うべきことであり、そのような変数を使用する標準(afaik)がないため安全です。)


長いオプションに対する予期しない引数について不平を言うには、必須の引数に対して私たちがしたことを真似て、ヘルパー関数を使用します。予想外の引数について文句を言うには、テストをひっくり返すだけです。

no_arg() { if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }

この回答古いバージョンでは、スペースで区切られた引数を持つ長いオプションを受け入れようとしましたが、信頼できませんでした。getopts引数がその範囲を超えていて、手動でインクリメントすること$OPTINDがすべてのシェルで機能するとは限らないという前提で、は途中で終了する可能性があります。

これは、次のいずれかの手法を使用して実現されます。

そして、次のようなもので終わりました [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))


非常に素晴らしい自己完結型ソリューション。1つの質問:letter-c引数は必要ないので、使用するだけでは不十分letter-c)でしょうか。これ*は冗長なようです。
Philip Kearns

1
@Arne位置引数は悪いUXです。それらは理解するのが難しく、オプションの引数はかなり厄介です。getoptsそれらを処理するように設計されていないため、最初の位置引数で停止します。これにより、独自の引数を持つサブコマンドが許可されます。たとえばgit diff --color、サブコマンドの引数として、およびサブコマンドの引数(オプション付き)command --foo=moo bar --baz wazとして持つと解釈します。これは上記のコードで行うことができます。議論が必要であり、それが別の選択肢ではないことが明確でないため、私は拒否します。--foocommand--baz wazbar--bravo -blah--bravo-blah
Adam Katz

1
UXについては意見が異なります。位置引数は、数を制限する限り(多くて2または1と同じ型のNに)、便利で簡単です。ユーザーはコマンドを段階的に構築できるため(つまり、ls abc -la)、キーワード引数を使用してそれらを散在させることができるはずです。
Arne Babenhauserheide

1
@AdamKatz:私はこれで小さな記事を書きました:draketo.de/english/free-software/shell-argument-parsing —後続のオプションをキャッチするために残りの引数を繰り返し読み取ることを含みます。
Arne Babenhauserheide 2017

1
@ArneBabenhauserheide:スペースで区切られた引数をサポートするために、この回答を更新しました。evalPOSIXシェルで必要なため、残りの回答の下に記載されています。
Adam Katz

33

ポータブルシェルライブラリであるshFlagsを見てください(Linux、Solarisなどでは、sh、bash、dash、ksh、zshなど)。

スクリプトに1行追加するのと同じくらい簡単に新しいフラグを追加でき、自動生成された使用法機能を提供します。

次に、shFlagHello, world!を使用した簡単な例を示します。

#!/bin/sh

# source shflags from current directory
. ./shflags

# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'

# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# say hello
echo "Hello, ${FLAGS_name}!"

長いオプションをサポートするgetoptが強化されたOS(Linuxなど)の場合、次のことができます。

$ ./hello_world.sh --name Kate
Hello, Kate!

残りについては、短いオプションを使用する必要があります。

$ ./hello_world.sh -n Kate
Hello, Kate!

新しいフラグの追加は、新しいの追加と同じくらい簡単DEFINE_ callです。


2
これは素晴らしいですが、残念ながら私のgetopt(OS X)は引数のスペースをサポートしていません:/代替案があるかどうか疑問に思います。
Alastair Stuart

@AlastairStuart-確かにOS Xに代替手段があります。MacPortsを使用してGNU getoptをインストールします(通常は/ opt / local / bin / getoptにインストールされます)。
アーバンバガボンド

3
@UrbanVagabond –残念ながら、システム以外のデフォルトツールのインストールは、十分にポータブルなツールの要件としては容認できません。
Alastair Stuart

@AlastairStuart – GNU getoptではなくgetopts ビルトインを使用するポータブルソリューションについての私の回答を参照しください。これは基本的なgetoptsの使用法と同じですが、長いオプションの追加の反復があります。
Adam Katz

31

getopts短い/長いオプションと引数で使用する


すべての組み合わせで機能します。例:

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

この例のいくつかの宣言

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

Usage関数の外観

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 長い/短いフラグと長い引数

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

出力

##################################################################
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"

上記をまとまりのあるスクリプトに組み合わせる

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

これは、複数の長い引数(-)では機能しません。私にとっては最初のものしか読んでいないようです。
Sinaesthetic

@Sinaesthetic –ええ、私はeval長いオプションで間隔を置いた引数のアプローチを試してみましたが、特定のシェルでは信頼できないことがわかりました(ただし、bashで動作することを期待していますが、その場合はを使用する必要はありませんeval)。で長いオプション引数を受け入れる方法と、スペースを使用するための私の試みについては、私の回答を参照しください=。このソリューションではcut数回使用していますが、私のソリューションでは外部呼び出しを行いません。
アダムKatz

24

別の方法...

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done

1
これは、$args再割り当てのたびにスペースを必要としませんか?これはbashismなしでも実行できますが、このコードはオプションと引数のスペースを失います(私は$delimトリックがうまくいくとは思いません)。最初の反復でのみそれを空にするように十分注意している場合は、代わりにループset 内で実行できforます。 これがバシズムのないより安全なバージョンです。
Adam Katz

18

私はこのように解決しました:

# A string with command options
options=$@

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo "key $argument value ${arguments[index]}" ;;
      -abc) echo "key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

私は馬鹿げているのか何かですか?getoptそしてgetopts、とても混乱しています。


1
これは私にはうまくいくようです、私はこの方法の問題が何であるかわかりませんが、それは単純なようですので、他の誰もがそれを使用していない理由があるはずです。
ビリームーン

1
@ビリーはい、パラメーターなどを管理するためにスクリプトを使用しないので、これは簡単です。基本的に、引数文字列($ @)を配列に変換し、それをループします。ループでは、現在の値がキーになり、次の値が値になります。そのような単純な。

1
@Theodoreこれがあなたのお役に立てて嬉しいです!私にとっても苦痛でした。興味がある場合は、ここで実際の例をご覧いただけ

2
間違いなく私が見た最も簡単な方法。exprの代わりにi = $(($ i + 1))を使用するなど、少し変更しましたが、コンセプトは気密です。
Thomas Dignan

6
まったく馬鹿ではありませんが、機能が不足している可能性があります。getopt(s)は、混合されたオプションを認識できます(例: -ltrまたは-lt -rと同様-l -t -r)。また、いくつかのエラー処理、およびオプションの処理が完了した後、処理されたパラメーターを簡単に移動する方法も提供します。
Olivier Dulac

14

getopt依存関係が必要ない場合は、これを行うことができます:

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

もちろん、1つのダッシュで長いスタイルのオプションを使用することはできません。また、短縮バージョン(--verboseではなく--verbosなど)を追加する場合は、手動で追加する必要があります。

ただし、getopts長いオプションとともに機能を取得したい場合は、これが簡単な方法です。

また、このスニペットを要旨に入れました。


これは一度に1つの長いオプションでのみ機能するようですが、私のニーズは満たしました。ありがとうございました!
kingjeffrey 2013年

特別なケース--)ではshift ;欠けているようです。現時点では、--オプション以外の最初の引数として残ります。
dgw 2014年

dgwが指摘するように、--オプションshiftはそこに必要ですが、これは実際にはより良い答えだと思います。選択肢はプラットフォームに依存するバージョンであるgetoptgetopts_long、コマンドの開始時にのみショートオプションを使用するよう強制する必要があるため(つまり、使用してgetoptsから長いオプションを後で処理するため)、これにより任意の順序が得られるため、これはより良いと私は言いますそして完全な制御。
Haravikk 2014

この答えは、私がこの絶対的に明確で簡単な解決策でしか実行できない仕事を行うために何十もの答えのスレッドがあるのか、そして証明以外に何十億ものgetopt(s)の使用例に理由があるのか疑問に思います自分。
Florian Heigl 2017

11

組み込みgetoptsはこれを行うことができません。これを実行できる外部のgetopt(1)プログラムがありますが、これはLinuxではutil-linuxパッケージからしか取得できません。サンプルスクリプトgetopt-parse.bashが付属しています。

getopts_longシェル関数として書かれたものもあります。


3
getopt1993年にFreeBSDのバージョン1.0に含まれていた、それ以来のFreeBSDの一部となっています。そのため、これはFreeBSD 4.xから採用され、AppleのDarwinプロジェクトに含まれています。OS X 10.6.8の時点では、Appleに含まれているマニュアルページは、FreeBSDのマニュアルページとまったく同じです。つまり、OSやLinux以外の他のオペレーティングシステムに含まれています。誤った情報に対するこの回答の-1。
ghoti 2012年

8
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo "[otheropts]==> $@"

exit

#!/bin/bash
until [ -z "$1" ]; do
  case $1 in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong $1"
        shift
      fi;;
    *) echo "==> other $1"; shift;;
  esac
done
exit

2
説明がいいでしょう。最初のスクリプトは短いオプションを受け入れますが、2番目のスクリプトは長いオプション引数の解析にバグがあります。その変数は"${1:0:1}"、引数#1、インデックス0のサブストリング、長さ1である必要があります。これにより、短いオプションと長いオプションを混在させることはできません。
Adam Katz

7

ではksh93getopts長い名前をサポートしていますか...

while getopts "f(file):s(server):" flag
do
    echo "$flag" $OPTIND $OPTARG
done

または私が見つけたチュートリアルは言った。試してみてください。


4
これはksh93のgetopts組み込みです。この構文とは別に、より短い構文なしで長いオプションを許可する、より複雑な構文もあります。
ジル

2
合理的な答え。OPはWHATシェルを指定しませんでした。
ghoti

6

私は時々シェルスクリプトを書くだけで、練習から外れるので、どんなフィードバックもありがたいです。

@Arvid Requateによって提案された戦略を使用して、いくつかのユーザーエラーに気づきました。値を含めるのを忘れたユーザーは、次のオプションの名前を誤って値として処理してしまいます。

./getopts_test.sh --loglevel= --toc=TRUE

「loglevel」の値は「--toc = TRUE」と見なされます。これは回避できます。

手動解析のhttp://mwiki.wooledge.org/BashFAQ/035ディスカッションから、CLIのユーザーエラーのチェックに関するいくつかのアイデアを採用しました 。「-」と「-」の両方の引数の処理にエラーチェックを挿入しました。

それから私は構文をいじり始めたので、ここでのエラーは厳密に私のせいであり、元の作者ではありません。

私のアプローチは、等号を付けて、または付けずに長く入力することを好むユーザーを支援します。つまり、「-loglevel = 9」に対する応答は「--loglevel = 9」と同じになります。-/ spaceメソッドでは、ユーザーが引数を忘れたかどうかを確実に知ることができないため、推測が必要です。

  1. ユーザーが長い/等号の形式(--opt =)を持っている場合、=の後にスペースがあると、引数が指定されなかったためエラーが発生します。
  2. ユーザーがロング/スペース引数(--opt)を持っている場合、引数が続かない場合(コマンドの終わり)、または引数がダッシュで始まる場合、このスクリプトは失敗します)

これを使い始める場合、 "-opt = value"と "--opt value"の形式には興味深い違いがあります。等号を使用すると、コマンドライン引数は「opt = value」と見なされ、「=」で区切るための文字列解析が処理されます。対照的に、「-opt value」では、引数の名前は「opt」であり、コマンドラインで次の値を取得するという課題があります。ここで、@ Arvid Requateが間接参照である$ {!OPTIND}を使用しました。私はまだそのことをまったく理解していません。BashFAQのコメントはそのスタイルに対して警告しているようです(http://mywiki.wooledge.org/BashFAQ/006)。ところで、OPTIND = $(($ OPTIND + 1))の重要性に関する以前の投稿者のコメントは正しいとは思いません。というか、

このスクリプトの最新バージョンでは、フラグ-vは詳細な出力を意味します。

「cli-5.sh」という名前のファイルに保存して実行可能にすると、これらのいずれかが機能するか、希望どおりに失敗します。

./cli-5.sh  -v --loglevel=44 --toc  TRUE
./cli-5.sh  -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9

./cli-5.sh  --toc FALSE --loglevel=77
./cli-5.sh  --toc=FALSE --loglevel=77

./cli-5.sh   -l99 -t yyy
./cli-5.sh   -l 99 -t yyy

ユーザーintpuのエラーチェックの出力例は次のとおりです

$ ./cli-5.sh  --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh  --toc= --loglevel=77
ERROR: value for toc undefined

OPTINDとOPTARGの内部を出力するため、-vをオンにすることを検討する必要があります。

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

# What I don't understand yet: 
# In @Arvid REquate's answer, we have 
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


die() {
    printf '%s\n' "$1" >&2
    exit 1
}

printparse(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
    fi
}

showme(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'VERBOSE: %s\n' "$1" >&2;
    fi
}


VERBOSE=0
loglevel=0
toc="TRUE"

optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do

    showme "OPTARG:  ${OPTARG[*]}"
    showme "OPTIND:  ${OPTIND[*]}"
    case "${OPTCHAR}" in
        -)
            case "${OPTARG}" in
                loglevel) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
                    printparse "--${OPTARG}" "  " "${val}"
                    loglevel="${val}"
                    shift
                    ;;
                loglevel=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        printparse "--${opt}" "=" "${val}"
                        loglevel="${val}"
                        ## shift CAUTION don't shift this, fails othewise
                    else
                        die "ERROR: $opt value must be supplied"
                    fi
                    ;;
                toc) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) #??
                    printparse "--${opt}" " " "${val}"
                    toc="${val}"
                    shift
                    ;;
                toc=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        toc=${val}
                        printparse "--$opt" " -> " "$toc"
                        ##shift ## NO! dont shift this
                    else
                        die "ERROR: value for $opt undefined"
                    fi
                    ;;

                help)
                    echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
                    exit 2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h|-\?|--help)
            ## must rewrite this for all of the arguments
            echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
            exit 2
            ;;
        l)
            loglevel=${OPTARG}
            printparse "-l" " "  "${loglevel}"
            ;;
        t)
            toc=${OPTARG}
            ;;
        v)
            VERBOSE=1
            ;;

        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"

OPTIND=$(( $OPTIND + 1 )):OPTINDのパラメーターを「取得」するときはいつでも必要です(例:使用されている場合--toc value :値がパラメーター番号$ OPTINDにある場合)tocの値として取得したら、次に解析するパラメーターが値ではないことをgetoptsに通知する必要があります。しかし、その後の1つ(つまり、:OPTIND=$(( $OPTIND + 1 )) 。とスクリプト(および参照するスクリプト)が欠落しています。完了後:shift $(( $OPTIND -1 ))(getoptsは、パラメーター1をOPTIND-1に解析した後に終了したため、シフトアウトする必要があります。$@残りの「非オプション」パラメータになります
Olivier Dulac '13

ああ、自分でシフトすると、getoptsの下のパラメーターが "シフト"されるので、OPTINDは常に正しいことを指しています...しかし、私はそれを非常に混乱させています。getoptsのwhileループの後にシフト$(($ OPTIND-1))が必要なので、今はスクリプトをテストできないので、$ 1は元の$ 1(オプション)を指していませんが、残りの引数の最初の引数(すべてのオプションとその値の後に来る引数)。例:myrm -foo -bar = baz thisarg thenthisone thenanother
Olivier Dulac

5

ホイールのさらに別のバージョンを発明しています...

この関数は、GNU getoptの(うまくいけば)POSIX互換のプレーンボーンシェルの置き換えです。必須/オプション/引数なしを受け入れることができる短い/長いオプションをサポートし、オプションの指定方法はGNU getoptとほとんど同じなので、変換は簡単です。

もちろん、これはまだスクリプトにドロップするかなり大きなコードの塊ですが、よく知られているgetopt_longシェル関数の約半分の行であり、既存のGNU getoptの使用を置き換えるだけの場合に適しています。

これはかなり新しいコードなので、YMMV(そして、これが実際に何らかの理由でPOSIX互換でない場合は、間違いなくお知らせください-移植性は当初からの意図でしたが、有用なPOSIXテスト環境がありません)。

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

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
    local param
    for param; do
        printf %s\\n "$param" \
            | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    printf %s\\n " "
}

# Exit with status $1 after displaying error message $2.
exiterr () {
    printf %s\\n "$2" >&2
    exit $1
}

# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts="$1"
    longopts="$2"
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "$1" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "$@")"
                shift $#
                break
            };;
            # process short option
            ( -[!-]* ) {         # -x[foo]
                suffix=${1#-?}   # foo
                opt=${1%$suffix} # -x
                optchar=${opt#-} # x
                case "${shortopts}" in
                    ( *${optchar}::* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *${optchar}:* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 "$1 requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 "$1 requires an argument";;
                            esac
                        fi
                    };;
                    ( *${optchar}* ) { # no argument
                        argtype=none
                        arg=
                        shift
                        # Handle multiple no-argument parameters combined as
                        # -xyz instead of -x -y -z. If we have just shifted
                        # parameter -xyz, we now replace it with -yz (which
                        # will be processed in the next iteration).
                        if [ -n "${suffix}" ]; then
                            eval "set -- $(save "-${suffix}")$(save "$@")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "$1" ]; then
                    suffix=
                fi
                opt=${1%=$suffix} # --xarg
                optword=${opt#--} # xarg
                case ",${longopts}," in
                    ( *,${optword}::,* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *,${optword}:,* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 \
                                       "--${optword} requires an argument";;
                            esac
                        fi
                    };;
                    ( *,${optword},* ) { # no argument
                        if [ -n "${suffix}" ]; then
                            exiterr 1 "--${optword} does not take an argument"
                        fi
                        argtype=none
                        arg=
                        shift
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option $1";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "$1")"; shift;;
        esac

        if [ -n "${opt}" ]; then
            getopt="${getopt}$(save "$opt")"
            case "${argtype}" in
                ( optional|required ) {
                    getopt="${getopt}$(save "$arg")"
                };;
            esac
        fi
    done

    # Generate function output, suitable for:
    # eval "set -- $(posix_getopt ...)"
    printf %s "${getopt}"
    if [ -n "${nonopt}" ]; then
        printf %s "$(save "--")${nonopt}"
    fi
}

使用例:

# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi

4

受け入れられた答えは、bash built-inのすべての欠点を指摘する非常に素晴らしい仕事をしgetoptsます。答えは次のように終わります:

したがって、長いオプションのサポートの欠如を回避するためにさらにコードを書くことは可能ですが、これははるかに多くの作業であり、コードを簡略化するgetoptパーサーを使用する目的を部分的に無効にします。

原則としてその声明に同意しますが、さまざまなスクリプトにこの機能をすべて実装した回数は、「標準化された」十分にテストされたソリューションの作成に少し労力を費やすことを正当化すると思います。

そのため、外部の依存関係なしで、純粋なbashでgetopts実装getopts_longすることにより、組み込みのbashを「アップグレード」しました。関数の使用法は、組み込みと100%互換性がありgetoptsます。

スクリプトに(GitHubgetopts_longホストされている)を含めることで、元の質問への回答を次のように簡単に実装できます。

source "${PATH_TO}/getopts_long.bash"

while getopts_long ':c: copyfile:' OPTKEY; do
    case ${OPTKEY} in
        'c'|'copyfile')
            echo 'file supplied -- ${OPTARG}'
            ;;
        '?')
            echo "INVALID OPTION -- ${OPTARG}" >&2
            exit 1
            ;;
        ':')
            echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
            exit 1
            ;;
        *)
            echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
            exit 1
            ;;
    esac
done

shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift

3

私はまだ彼の解決策をコメントまたは投票するのに十分な担当者がいませんが、smeの答えは私にとって非常にうまくいきました。私が遭遇した唯一の問題は、引数が単一引用符で囲まれてしまうことでした(そのため、それらを取り除いています)。

また、使用例とヘルプテキストも追加しました。少し拡張したバージョンをここに含めます。

#!/bin/bash

# getopt example
# from: /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag=true ;;
    -b|--blong) bflag=true ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    -h|--help|-\?) echo -e $HELP_TEXT; exit;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done

3

ここでは、bashで複雑なオプションを解析するためのいくつかの異なるアプローチを見つけることができます:http : //mywiki.wooledge.org/ComplexOptionParsing

次のコードを作成しましたが、それは最小限のコードであり、長いオプションと短いオプションの両方が機能するため、良いものだと思います。このアプローチでは、長いオプションに複数の引数を指定することもできます。

#!/bin/bash
# Uses bash extensions.  Not portable as written.

declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
    case "${opt}" in
        -) #OPTARG is name-of-long-option or name-of-long-option=value
            if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
            then
                opt=${OPTARG/=*/}
                OPTARG=${OPTARG#*=}
                ((OPTIND--))    
            else #with this --key value1 value2 format multiple arguments are possible
                opt="$OPTARG"
                OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
            fi
            ((OPTIND+=longoptspec[$opt]))
            continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
            ;;
        loglevel)
          loglevel=$OPTARG
            ;;
        h|help)
            echo "usage: $0 [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file

2

私はその主題にかなり長い間取り組んできました...メインスクリプトでソースする必要がある独自のライブラリを作成しました。例については、libopt4shellおよびcd2mpcを参照してください。それが役に立てば幸い !


2

改善されたソリューション:

# 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

2

たぶん、長いコマンドラインオプションが必要な場合は、getoptsの部分だけでkshを使用する方が簡単です。

# Working Getopts Long => KSH

#! /bin/ksh
# Getopts Long
USAGE="s(showconfig)"
USAGE+="c:(createdb)"
USAGE+="l:(createlistener)"
USAGE+="g:(generatescripts)"
USAGE+="r:(removedb)"
USAGE+="x:(removelistener)"
USAGE+="t:(createtemplate)"
USAGE+="h(help)"

while getopts "$USAGE" optchar ; do
    case $optchar in
    s)  echo "Displaying Configuration" ;;
        c)  echo "Creating Database $OPTARG" ;;
    l)  echo "Creating Listener LISTENER_$OPTARG" ;;
    g)  echo "Generating Scripts for Database $OPTARG" ;;
    r)  echo "Removing Database $OPTARG" ;;
    x)  echo "Removing Listener LISTENER_$OPTARG" ;;
    t)  echo "Creating Database Template" ;;
    h)  echo "Help" ;;
    esac
done

+1-これはksh93に限定されていることに注意してください-オープンソースのASTプロジェクト(AT&T Research)によるものです。
ヘンクランゲヴェルド

2

厳密なbashサポート(-u)を使用して、外部依存関係のないものが必要でした。古いバージョンのbashでも動作させる必要がありました。これはさまざまなタイプのパラメーターを処理します。

  • 短いブール(-h)
  • 短いオプション(-i "image.jpg")
  • 長いブール(--help)
  • 等しいオプション(--file = "filename.ext")
  • スペースオプション(--file "filename.ext")
  • 連結ブール(-hvm)

スクリプトの先頭に次のコードを挿入するだけです。

# Check if a list of params contains a specific param
# usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ...
# the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
_param_variant() {
  for param in $1 ; do
    local variants=${param//\|/ }
    for variant in $variants ; do
      if [[ "$variant" = "$2" ]] ; then
        # Update the key to match the long version
        local arr=(${param//\|/ })
        let last=${#arr[@]}-1
        key="${arr[$last]}"
        return 0
      fi
    done
  done
  return 1
}

# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
#     # First, set your defaults
#     param_help=false
#     param_path="."
#     param_file=false
#     param_image=false
#     param_image_lossy=true
#     # Define allowed parameters
#     allowed_params="h|?|help p|path f|file i|image image-lossy"
#     # Get parameters from the arguments provided
#     _get_params $*
#
# Parameters will be converted into safe variable names like:
#     param_help,
#     param_path,
#     param_file,
#     param_image,
#     param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
#     -i "path/goes/here"
#     --image "path/goes/here"
#     --image="path/goes/here"
#     --image=path/goes/here
# These would all result in effectively the same thing:
#     param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
#     -vhm is the same as -v -h -m
_get_params(){

  local param_pair
  local key
  local value
  local shift_count

  while : ; do
    # Ensure we have a valid param. Allows this to work even in -u mode.
    if [[ $# == 0 || -z $1 ]] ; then
      break
    fi

    # Split the argument if it contains "="
    param_pair=(${1//=/ })
    # Remove preceeding dashes
    key="${param_pair[0]#--}"

    # Check for concatinated boolean short parameters.
    local nodash="${key#-}"
    local breakout=false
    if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
      # Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
      local short_param_count=${#nodash}
      let new_arg_count=$#+$short_param_count-1
      local new_args=""
      # $str_pos is the current position in the short param string $nodash
      for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
        # The first character becomes the current key
        if [ $str_pos -eq 0 ] ; then
          key="${nodash:$str_pos:1}"
          breakout=true
        fi
        # $arg_pos is the current position in the constructed arguments list
        let arg_pos=$str_pos+1
        if [ $arg_pos -gt $short_param_count ] ; then
          # handle other arguments
          let orignal_arg_number=$arg_pos-$short_param_count+1
          local new_arg="${!orignal_arg_number}"
        else
          # break out our one argument into new ones
          local new_arg="-${nodash:$str_pos:1}"
        fi
        new_args="$new_args \"$new_arg\""
      done
      # remove the preceding space and set the new arguments
      eval set -- "${new_args# }"
    fi
    if ! $breakout ; then
      key="$nodash"
    fi

    # By default we expect to shift one argument at a time
    shift_count=1
    if [ "${#param_pair[@]}" -gt "1" ] ; then
      # This is a param with equals notation
      value="${param_pair[1]}"
    else
      # This is either a boolean param and there is no value,
      # or the value is the next command line argument
      # Assume the value is a boolean true, unless the next argument is found to be a value.
      value=true
      if [[ $# -gt 1 && -n "$2" ]]; then
        local nodash="${2#-}"
        if [ "$nodash" = "$2" ]; then
          # The next argument has NO preceding dash so it is a value
          value="$2"
          shift_count=2
        fi
      fi
    fi

    # Check that the param being passed is one of the allowed params
    if _param_variant "$allowed_params" "$key" ; then
      # --key-name will now become param_key_name
      eval param_${key//-/_}="$value"
    else
      printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
    fi
    shift $shift_count
  done
}

そしてそれを次のように使用します:

# Assign defaults for parameters
param_help=false
param_path=$(pwd)
param_file=false
param_image=true
param_image_lossy=true
param_image_lossy_quality=85

# Define the params we will allow
allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"

# Get the params from arguments provided
_get_params $*

1

クロスプラットフォームの互換性を維持し、外部の実行可能ファイルへの依存を回避するために、一部のコードを別の言語から移植しました。

私はそれが非常に使いやすいと思います、ここに例があります:

ArgParser::addArg "[h]elp"    false    "This list"
ArgParser::addArg "[q]uiet"   false    "Supress output"
ArgParser::addArg "[s]leep"   1        "Seconds to sleep"
ArgParser::addArg "v"         1        "Verbose mode"

ArgParser::parse "$@"

ArgParser::isset help && ArgParser::showArgs

ArgParser::isset "quiet" \
   && echo "Quiet!" \
   || echo "Noisy!"

local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
   && echo "Sleep for $__sleep seconds" \
   || echo "No value passed for sleep"

# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"

必要なBASHはそれよりも少し長いですが、BASH 4の連想配列に依存しないようにしたいと思いました。これは、http://nt4.com/bash/argparser.inc.shから直接ダウンロードすることもできます。

#!/usr/bin/env bash

# Updates to this script may be found at
# http://nt4.com/bash/argparser.inc.sh

# Example of runtime usage:
# mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com

# Example of use in script (see bottom)
# Just include this file in yours, or use
# source argparser.inc.sh

unset EXPLODED
declare -a EXPLODED
function explode 
{
    local c=$# 
    (( c < 2 )) && 
    {
        echo function "$0" is missing parameters 
        return 1
    }

    local delimiter="$1"
    local string="$2"
    local limit=${3-99}

    local tmp_delim=$'\x07'
    local delin=${string//$delimiter/$tmp_delim}
    local oldifs="$IFS"

    IFS="$tmp_delim"
    EXPLODED=($delin)
    IFS="$oldifs"
}


# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
# Usage: local "$1" && upvar $1 "value(s)"
upvar() {
    if unset -v "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
        fi
    fi
}

function decho
{
    :
}

function ArgParser::check
{
    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        matched=0
        explode "|" "${__argparser__arglist[$i]}"
        if [ "${#1}" -eq 1 ]
        then
            if [ "${1}" == "${EXPLODED[0]}" ]
            then
                decho "Matched $1 with ${EXPLODED[0]}"
                matched=1

                break
            fi
        else
            if [ "${1}" == "${EXPLODED[1]}" ]
            then
                decho "Matched $1 with ${EXPLODED[1]}"
                matched=1

                break
            fi
        fi
    done
    (( matched == 0 )) && return 2
    # decho "Key $key has default argument of ${EXPLODED[3]}"
    if [ "${EXPLODED[3]}" == "false" ]
    then
        return 0
    else
        return 1
    fi
}

function ArgParser::set
{
    key=$3
    value="${1:-true}"
    declare -g __argpassed__$key="$value"
}

function ArgParser::parse
{

    unset __argparser__argv
    __argparser__argv=()
    # echo parsing: "$@"

    while [ -n "$1" ]
    do
        # echo "Processing $1"
        if [ "${1:0:2}" == '--' ]
        then
            key=${1:2}
            value=$2
        elif [ "${1:0:1}" == '-' ]
        then
            key=${1:1}               # Strip off leading -
            value=$2
        else
            decho "Not argument or option: '$1'" >& 2
            __argparser__argv+=( "$1" )
            shift
            continue
        fi
        # parameter=${tmp%%=*}     # Extract name.
        # value=${tmp##*=}         # Extract value.
        decho "Key: '$key', value: '$value'"
        # eval $parameter=$value
        ArgParser::check $key
        el=$?
        # echo "Check returned $el for $key"
        [ $el -eq  2 ] && decho "No match for option '$1'" >&2 # && __argparser__argv+=( "$1" )
        [ $el -eq  0 ] && decho "Matched option '${EXPLODED[2]}' with no arguments"        >&2 && ArgParser::set true "${EXPLODED[@]}"
        [ $el -eq  1 ] && decho "Matched option '${EXPLODED[2]}' with an argument of '$2'"   >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift
        shift
    done
}

function ArgParser::isset
{
    declare -p "__argpassed__$1" > /dev/null 2>&1 && return 0
    return 1
}

function ArgParser::getArg
{
    # This one would be a bit silly, since we can only return non-integer arguments ineffeciently
    varname="__argpassed__$1"
    echo "${!varname}"
}

##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
    local __varname="__argpassed__$1"
    local __value="${!__varname}"
    test -z "$__value" && return 1
    local "$3" && upvar $3 "$__value"
    return 0
}

function ArgParser::__construct
{
    unset __argparser__arglist
    # declare -a __argparser__arglist
}

##
# @brief add command line argument
# @param 1 short and/or long, eg: [s]hort
# @param 2 default value
# @param 3 description
##
function ArgParser::addArg
{
    # check for short arg within long arg
    if [[ "$1" =~ \[(.)\] ]]
    then
        short=${BASH_REMATCH[1]}
        long=${1/\[$short\]/$short}
    else
        long=$1
    fi
    if [ "${#long}" -eq 1 ]
    then
        short=$long
        long=''
    fi
    decho short: "$short"
    decho long: "$long"
    __argparser__arglist+=("$short|$long|$1|$2|$3")
}

## 
# @brief show available command line arguments
##
function ArgParser::showArgs
{
    # declare -p | grep argparser
    printf "Usage: %s [OPTION...]\n\n" "$( basename "${BASH_SOURCE[0]}" )"
    printf "Defaults for the options are specified in brackets.\n\n";

    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        local shortname=
        local fullname=
        local default=
        local description=
        local comma=

        explode "|" "${__argparser__arglist[$i]}"

        shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: 
        fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
        test -n "$shortname" \
            && test -n "$fullname" \
            && comma=","

        default="${EXPLODED[3]}"
        case $default in
            false )
                default=
                ;;
            "" )
                default=
                ;;
            * )
                default="[$default]"
        esac

        description="${EXPLODED[4]}"

        printf "  %2s%1s %-19s %s %s\n" "$shortname" "$comma" "$fullname" "$description" "$default"
    done
}

function ArgParser::test
{
    # Arguments with a default of 'false' do not take paramaters (note: default
    # values are not applied in this release)

    ArgParser::addArg "[h]elp"      false       "This list"
    ArgParser::addArg "[q]uiet" false       "Supress output"
    ArgParser::addArg "[s]leep" 1           "Seconds to sleep"
    ArgParser::addArg "v"           1           "Verbose mode"

    ArgParser::parse "$@"

    ArgParser::isset help && ArgParser::showArgs

    ArgParser::isset "quiet" \
        && echo "Quiet!" \
        || echo "Noisy!"

    local __sleep
    ArgParser::tryAndGetArg sleep into __sleep \
        && echo "Sleep for $__sleep seconds" \
        || echo "No value passed for sleep"

    # This way is often more convienient, but is a little slower
    echo "Sleep set to: $( ArgParser::getArg sleep )"

    echo "Remaining command line: ${__argparser__argv[@]}"

}

if [ "$( basename "$0" )" == "argparser.inc.sh" ]
then
    ArgParser::test "$@"
fi

1

すべての長いオプションに一意で一致する最初の文字が短いオプションとして含まれている場合、たとえば

./slamm --chaos 23 --plenty test -quiet

と同じです

./slamm -c 23 -p test -q

getoptsの前にこれを使用して、$ argsを書き換えることができます。

# change long options to short options

for arg; do 
    [[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
    if [ "${arg:0:2}" == "--" ]; 
       then args="${args} -${arg:2:1}" 
       else args="${args} ${delim}${arg}${delim}"
    fi
done

# reset the incoming args
eval set -- $args

# proceed as usual
while getopts ":b:la:h" OPTION; do
    .....

インスピレーションを与えてくれたmtveeに感謝;-)


私はここでevalの重要性を
理解

1

これがスクリプトを呼び出す方法である場合

myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"

次に、getoptと--longoptionsの助けを借りてそれを達成するためのこの最も簡単な方法に従うことができます

これを試して、これが役に立つことを願って

# Read command line options
ARGUMENT_LIST=(
    "input1"
    "input2"
    "input3"
)



# read arguments
opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "$0")" \
    --options "" \
    -- "$@"
)


echo $opts

eval set --$opts

while true; do
    case "$1" in
    --input1)  
        shift
        empId=$1
        ;;
    --input2)  
        shift
        fromDate=$1
        ;;
    --input3)  
        shift
        toDate=$1
        ;;
      --)
        shift
        break
        ;;
    esac
    shift
done

0

getoptsは、引数を持つことが期待されていない限り、長いオプションの解析に「使用できます」...

方法は次のとおりです。

$ 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

長いオプションのパラメーターを取得するためにOPTINDを使用しようとすると、getoptsはそれを最初のオプションの位置パラメーターなしとして扱い、他のパラメーターの解析を停止します。このような場合は、簡単なcaseステートメントを使用して手動で処理する方がよいでしょう。

これは「常に」機能します。

$ 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

とはいえ、getoptsほど柔軟ではなく、ケースインスタンス内でエラーチェックコードの多くを自分で行う必要があります...

しかし、それはオプションです。


しかし、長いオプションはしばしば議論を期待します。そして、それはハックのようなものであるにもかかわらず、それを機能させるために- 最後に一つは、それがネイティブにそれを実現するためのあらゆる方法がハッキングのようなものですが、それにもかかわらず、あなたが拡張することができ、それをサポートしていない場合と主張する可能性-をあまりにも。そして、はいシフトは非常に便利ですが、もちろん引数を期待している場合、次の引数(ユーザーが指定していない場合)が期待される引数の一部になる可能性があります。
プリフタン

はい、これは引数のない長い引数名のpocです。2つを区別するには、getopsなどの設定が必要です。シフトに関しては、セットでいつでも「戻す」ことができます。いずれの場合も、パラメーターが予期されるかどうかにかかわらず、構成可能でなければなりません。マジックを使用することもできますが、ユーザーに使用を強制します-マジックが停止し、位置パラメーターが開始することを通知します。
エスタニ

けっこうだ。それは合理的です。Tbh自分が何をしていたのかさえ覚えておらず、この質問自体をすっかり忘れていました。どうやってそれを見つけたのか、漠然と覚えているだけです。乾杯。ああ、アイデアの+1があります。あなたは努力を重ね、あなたは何をしているのかを明確にしました。私は他の人にアイデアなどを与えるために努力を重ねている人々を尊重します
プリフタン

0

組み込みは、getopts(は、ksh93を除く)のみパース短いオプション、あなたはまだgetoptsはハンドルの長いオプションを作るためのスクリプトの数行を追加することができます。

ここに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))

ここにテストがあります:

# 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

それ以外の場合、最近のKorn Shell ksh93では、getopts長いオプションを自然に解析でき、manページも同様に表示できます。(http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-optionsを参照してください


0

組み込みOS X(BSD)のgetoptは長いオプションをサポートしていませんが、GNUバージョンはサポートしていますbrew install gnu-getopt。次に、次のようになりますcp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt


0

EasyOptionsは、短いオプションと長いオプションを処理します。

## 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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.