bash getopts、短いオプションのみ、すべて値が必要、独自の検証


8

さまざまなオプションを受け入れるシェルスクリプトを構築しようとしgetoptsています。オプションと引数の変数の順序を処理できるため、良い解決策のようです(私はそう思います!)。

私は短いオプションのみを使用し、各短いオプションには対応する値が必要です。例:./command.sh -a arga -g argg -b argbほとんどの人がシェルコマンドでの作業に慣れているように、オプションを特定の順序で入力できないようにしたい。

もう1つのポイントは、オプションの引数値のチェックを自分自身で、理想的にはcaseステートメント内で行いたいということです。その理由は:)、私のcase声明での私のテストで一貫性のない結果が得られたためです(おそらく私の理解が不足しているためです)。
例えば:

#!/bin/bash
OPTIND=1 # Reset if getopts used previously
if (($# == 0)); then
        echo "Usage"
        exit 2
fi
while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                :)
                       echo "Option -$OPTARG requires an argument" >&2
                       exit 2;;
        esac
done
shift $((OPTIND-1))
echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

このような発生で失敗しました... ./command.sh -d -h
-dに引数が必要なフラグを付けたいのですが、その値が-d=-h必要なものではありません。

したがって、caseステートメント内で独自の検証を実行して、各オプションが設定され、1回だけ設定されるようにする方が簡単だと考えました。

以下を実行しようとしていますが、if [ ! "$MYSQL_HOST" ]; thenブロックがトリガーされません。

OPTIND=1 # Reset if getopts used previously

if (($# == 0)); then
        echo "Usage"
        exit 2
fi

while getopts ":h:u:p:d:" opt; do
        case "$opt" in

                h)
                        MYSQL_HOST=$OPTARG
                        if [ ! "$MYSQL_HOST" ]; then
                                echo "host not set"
                                exit 2
                        fi
                        ;;
                u)
                        MYSQL_USER=$OPTARG
                        if [ ! "$MYSQL_USER" ]; then
                                echo "username not set"
                                exit 2
                        fi
                        ;;
                p)
                        MYSQL_PASS=$OPTARG
                        if [ ! "$MYSQL_PASS" ]; then
                                echo "password not set"
                                exit 2
                        fi
                        ;;
                d)
                        BACKUP_DIR=$OPTARG
                        if [ ! "$BACKUP_DIR" ]; then
                                echo "backup dir not set"
                                exit 2
                        fi
                        ;;
                \?)
                        echo "Invalid option: -$OPTARG" >&2
                        exit 2;;
                #:)
                #       echo "Option -$opt requires an argument" >&2
                #       exit 2;;
        esac
done
shift $((OPTIND-1))

echo "MYSQL_HOST='$MYSQL_HOST'  MYSQL_USER='$MYSQL_USER'  MYSQL_PASS='$MYSQL_PASS'  BACKUP_DIR='$BACKUP_DIR' Additionals: $@"

OPTARG内部から長さがゼロかどうかを確認できない理由はありますgetopts ... while ... caseか?

私自身の引数の検証を実行するためのより良い方法は何だgetopts、私はに頼ることにしたくない場合には:)while ... case ... esac?の外で引数の検証を実行します。
次に、引数の値が-detcになり、不足しているオプションをキャッチできなくなる可能性があります。

回答:


4

2番目のスクリプト(私はとして保存しましたgetoptit)を次のように呼び出すと、

getoptit -d -h

これは印刷されます:

MYSQL_HOST=''  MYSQL_USER=''  MYSQL_PASS=''  BACKUP_DIR='-h' Additionals: 

そのため、BACKUP_DIRが設定されており、設定されてif [ ! "$BACKUP_DIR" ]; thenいないかどうかをテストしているので、その中のコードがトリガーされないのが普通です。

各オプションが1度設定されているかどうかをテストする場合は、$ OPTARG値から割り当てを行う前に行う必要があります。また'-'-d -h割り当てる前に、$ OPTARGが(エラーの)で始まることも確認する必要があります。

...
            d)
                    if [ ! -z "$BACKUP_DIR" ]; then
                            echo "backup dir already set"
                            exit 2
                    fi
                    if [ z"${OPTARG:0:1}" == "z-" ]; then
                            echo "backup dir starts with option string"
                            exit 2
                    fi
                    BACKUP_DIR=$OPTARG
                    ;;
...

素晴らしい説明。whileオプションに対して実行されているループが実際にあるという事実について考えたら、完全に理にかなっています。これは、他の方法と比較して冗長であるにもかかわらず、スクリプトで何が行われているのかが非常に明確であるため、私が行ってきた方法です。そして、この場合、他者による保守性が重要です。
batfastad 2013年

3

他の答えは実際にはそれほどあなたの質問に答えないので:これは予想される動作であり、POSIXがどのように解釈されているかに基づいています:

オプションにオプション引数が必要な場合、getoptsユーティリティはそれをシェル変数OPTARGに配置します。オプションが見つからなかった場合、または見つかったオプションにオプション引数がない場合、OPTARGは設定解除されます。)

これが述べられているすべてであり、(それほど長くない)話を短く言えば、引数を持つ必要がgetoptsあるオプションについて、完全に有効な引数がオプション引数ではなく別のオプションであることを魔法のように知らない。オプションへの有効な引数として持つことを妨げるものは何もありません。つまり、実際には、引数を必要とするオプションがコマンドラインで最後に来た場合にのみエラーがスローされます。例:-h-dgetopts

test.sh

#!/bin/sh

while getopts ":xy:" o; do
    case "${o}" in
        :) echo "${OPTARG} requires an argument"; exit 1;
    esac
done

例:

$ ./test.sh -y -x
$

$ ./test.sh -x -y
y requires an argument

2番目のアプローチが機能しない理由は、機能する構文解析getoptsがまだ同じであるため、ループ内に入ると、「損傷」が既に行われているためです。

絶対にこれを禁止したい場合は、Benubirdが指摘したように、オプションの引数を自分で確認し、それらが有効なオプションと等しい場合はエラーをスローする必要があります。


2

私は使い続けますgetoptsが、後でいくつかの追加チェックを行います:(テストされていません)

errs=0
declare -A option=(
    [MYSQL_HOST]="-h"
    [MYSQL_USER]="-u"
    [MYSQL_PASS]="-p"
    [BACKUP_DIR]="-d" 
)
for var in "${!option[@]}"; do
    if [[ -z "${!var}" ]]; then
        echo "error: specify a value for $var with ${option[var]}"
        ((errs++))
    fi
done
((errs > 0)) && exit 1

bashバージョン4が必要


1

Gnuの非シェル組み込みgetoptはあまり効果がありませんが、フラグを見つけた後に何が起こるか(自分で引数をシフトオフするなど)を決定できるので、柔軟性が向上します。

getoptsとgetopt比較する記事を見つけました。マニュアルは(少なくともコーヒーを飲む前に)理解するのが少し難しいため、おそらく役立つでしょう。


1

まず、を使用する必要がありますif [ -z "$MYSQL_USER" ]

第二に、割り当ての必要はありません- if [ -z "$OPTARG" ]正常に動作します。

第三に、あなたが実際に望んでいるのはだと思いますif [ ${#OPTARG} = 0 ]。$ {#x}はbashで、文字列$ xの長さを返します(詳細はこちらを参照)。

第4に、独自の検証を行う場合は、getoptsではなくgetoptをお勧めします。これにより、柔軟性が大幅に向上します。

最後に、最初の質問に答えるために、次のようにフラグのリストを上部に配置することで、フラグが引数として渡されたことを検出できます。

args=( -h -u -p -d )

次に、指定された引数がオプションであるかどうかを確認するオプションで、次のようなチェックを行います。

d)
    BACKUP_DIR=$OPTARG
    if [ "$(echo ${args[@]/$OPTARG/})" = "$(echo ${args[@]})" ]
    then
        echo "Argument is an option! Error!"
    fi

完璧な答えではありませんが、うまくいきます!あなたの問題は、 "-h"が完全に有効な引数であり、有効なフラグではないパラメーターを探しているだけであることをシェルが知る方法がないことです。

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