rcmdnk's blog

Getopts

シェルスクリプトでgetoptsを使うと簡単に引数のオプションが解析が出来ますが、 ポジショナルな引数と一緒に使おうと思うと 解析部分が途中で終わってしまったりうまく行かないことがあります。

ただ使う側としては位置を気にせず使えたほうが便利なことが多いので それを実装する方法について。

getoptsを使ったオプション解析

よくある感じはこんなの。

arg.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env bash

var1="aaa"
var2="bbb"
var3="ccc"

while getopts ab:c:h OPT;do
  case $OPT in
    "a" ) var1="xxx";;
    "b" ) var2="$OPTARG";;
    "c" ) var3="$OPTARG";;
    "h" ) echo "This is help.";exit 0;;
    \? ) echo "Invalid option."; exit 1;;
  esac
done
shift $((OPTIND - 1))

echo "$var1"
echo "$var2"
echo "$var3"
echo "$@"

getoptsの後に-始まりで使いたいオプションとして使いたいアルファベットを書きますが、 値を持つものに関しては:を後ろにつけます。

このオプションのアルファベットがOPTに入り、 値持ちの場合はOPTARGにこの値が入ります1

それぞれのオプションに関してcase文でそれぞれに対しての処置を設定します。 もし-付きの引数でgetoptに定義されてないものがくるとOPTには?が入るのでそれに対する処置も追加しておきます。(エスケープ(\?)が必要。)

解析が一通り終わった後、解析に使われなかった残りの引数を取得するために 解析に使われた分(OPTIND - 1)2 だけshiftで削除して残りをポジショナルな引数($1, $2, …)として使えるような形にしています。

実行してみると、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ./arg.sh
aaa
bbb
ccc

$ ./arg.sh -a -b yyy -c zzz abc def
xxx
yyy
zzz
abc def
$ ./arg.sh -b yyy -a -c zzz -c ZZZ abc def
xxx
yyy
ZZZ
abc def

みたいな感じになります。 オプション部分に関しては順番は入れ替わっても大丈夫ですし、 複数回同じオプションを使ってもその度に解析されるので、 上の例だとただ値を入れてるだけなので-c zzz -c ZZZみたいにした場合は後の方が残っています。

ただ、これをオプションより前にポジショナルな引数を与えてしまうと

1
2
3
4
5
$ ./arg.sh abc -a -b yyy -c zzz def
aaa
bbb
ccc
abc -a -b yyy -c zzz def

といった感じで解析されません。

また、aは値を持たないオプションですが、-aの後ろに引数を置くとポジショナルな引数としてそこで解析が終わるので、

1
2
3
4
5
$ ./arg.sh -a abc -b yyy -c zzz def
xxx
bbb
ccc
abc -b yyy -c zzz def

といった感じに-aの解析だけ行われて終了してしまいます。

ポジショナルな引数をオプションの前や間にも入れられるようにする

これに関してはstackoverflowに10年以上前の質問がありました。

一番voteされてるのは普通のgetoptsの説明で答えになってない感じではあるんですが、 8年経って投稿された答えがいい感じに解決してくれてます。

上のスクリプトを下のように書き換えてみます。

arg.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env bash

var1="aaa"
var2="bbb"
var3="ccc"

positional=()
while [ "$OPTIND" -le "$#" ];do
  if getopts ab:c:h OPT;then
    case $OPT in
      "a" ) var1="xxx";;
      "b" ) var2="$OPTARG";;
      "c" ) var3="$OPTARG";;
      "h" ) echo "This is help.";exit 0;;
      \? ) echo "Invalid option."; exit 1;;
    esac
  else
    positional+=("${!OPTIND}")
    ((OPTIND++))
  fi
done

echo "$var1"
echo "$var2"
echo "$var3"
echo "${positional[@]}"

これを実行すると

1
2
3
4
5
$ ./arg.sh -a abc -b yyy -c zzz def
xxx
yyy
zzz
abc def

といった感じでオプション解析に使われてないものが positionalに入れられてそれを順に使えるような状態が出来ます。

ここでは

1
2
while [ "$OPTIND" -le "$#" ];do
  if getopts ab:c:h OPT;then

の組み合わせにすることでgetoptsで1つずつ解析していき、 かつ-始まりでないポジショナルな引数が来たらこの2段目がfalseになるので、 そこでは

1
2
positional+=("${!OPTIND}")
((OPTIND++))

としてpositional変数にその値を追加しています 3。 この場合はgetoptsでのOPTINDのインクリメントが行われないので 手動で1つ値を加えておきます。

こうすることで最後までオプション引数があれば解析してその他はポジショナルな引数として扱うことが出来ます。

オプションでない-で始まる引数をポジショナルなものとして扱う

上のスクリプトでオプションとして設定していない文字を-付きで与えると、

1
2
3
$ ./arg.sh -d
./arg.sh: illegal option -- d
Invalid option.

といった感じのエラーを出します。

このような設定していないものはそのままポジショナルな引数として使いたい、といったことがある場合は以下のようにすることで扱えます。

arg.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env bash

var1="aaa"
var2="bbb"
var3="ccc"

positional=()
while [ "$OPTIND" -le "$#" ];do
  if getopts :ab:c:h OPT;then
    case $OPT in
      "a" ) var1="xxx";;
      "b" ) var2="$OPTARG";;
      "c" ) var3="$OPTARG";;
      "h" ) echo "This is help.";exit 0;;
      \?) positional+=("-$OPTARG");;
    esac
  else
    positional+=("${!OPTIND}")
    ((OPTIND++))
  fi
done

echo "$var1"
echo "$var2"
echo "$var3"
echo "${positional[@]}"

変わったところはgetoptsの引数の最初に:が追加され、?の場合に positional-を加えたOPTARGを追加している部分。

getoptsの第一引数の文字列で一番最初に:を入れると -から始まる引数が指定のオプションでない場合はエラーにせずに その文字をOPTARGに入れる、という仕組みがあります。

この場合もOPTに入るのは?です。

これを使って-$OPTARGpositionalに加えることでオプション指定していない-引数をポジショナルな引数として扱っています。

1
2
3
4
5
$ ./arg.sh -d
aaa
bbb
ccc
-d

こんな感じ。

引数の一部を解析した上で さらに別の関数にそのまま渡したい、とか言う場合に使えそうです。

Sponsored Links
  1. OPTARGは決まった変数名ですが OPTの方はgetoptsの第二引数で与える名前で何でもOK。

  2. OPTINDはスクリプト開始時に1に初期化されていて、 getoptsで解析が行われるたび、1もしくは2(値ありの場合)増えます。

    なので上のwhile分で終わった際には実際に使われた引数+1の値になってるので1を引いてshiftします。

  3. ${!OPTIND}OPTINDが表す数字Nの$Nと同じ。

Sponsored Links

« macOSでトラックポイントでスクロールする inherit-docstring: Pythonでdocstringの一部を継承するdecorator »

}