シェルスクリプトに挑戦しよう(20)オプションの処理[その2]――長いオプション“応用力”をつけるためのLinux再入門(40)

前回に引き続き、今回のテーマも「オプション」です。今回は「getopt」などを使わずに処理します。シェルスクリプトでオプションを扱いたい場合、どのような点に注意するかも併せて考えていきましょう。

» 2020年03月25日 05時00分 公開
[西村めぐみ@IT]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「“応用力”をつけるためのLinux再入門」のインデックス

“応用力”をつけるためのLinux再入門

長いオプションを処理する

 まずは、「長いオプション」について扱います。変数名などは、前回作成したシェルスクリプトを使っています。

 この時点では、まだ、引数については受け皿とする変数を用意しているのみで、具体的な処理は行っていません。また、25行目の「ハイフン2つのみの文字列の後は全て引数」についても未処理です。

 1 #! /bin/bash
 2 
 3 # 長いオプション--aaa,--bbb,-cccを使用
 4 # --aaaは任意で引数を取り、--cccは引数が必須
 5 # ――という想定の処理を行うシェルスクリプト
 6 
 7 flag_a=false #変数「flag_a」にコマンド名「false」(第39回参照)をセット
 8 flag_b=false
 9 flag_c=false
10 arg_a=
11 arg_c=
12 opterr=false
13 declare -a argv
14 
15 # オプションの確認 --------------------
16 
17 # 引数を1つずつチェックし、flag_a, b, c および arg_a, cにセット
18 
19 while [ $# -gt 0 ]
20 do
21   case $1 in
22     --aaa) flag_a=true;;
23     --bbb) flag_b=true;;
24     --ccc) flag_c=true;;
25     --) shift; break;; # この後は全て引数
26     -*) opterr=true; echo "不明なオプション: $1";;
27     *)  argv+=("$1");;             # 配列argvに値を追加、空白を含んでいるかもしれないので""を付ける
28   esac
29   shift
30 done
31 
▲getoptを使わずに長いオプションを処理するシェルスクリプト「shopt2.sh」の例(1)(先頭の数字は行数、実際のスクリプトには含まれない、以下同)

 新しい変数として、オプション指定にエラーがあるかどうかを保存する「opterr」(12行目)と、引数を保存する配列「argv」(13行目)を用意しました。

 シンプルなcase文で、「--aaa」「--bbb」「--ccc」、および「--」のみの引数がないかどうかを確認しています。

 次の「-*」(26行目)というパターンは、ハイフンに続く任意の文字列ということで、「--aaa」「--bbb」「--ccc」「--」以外のものについてはエラー扱いとし、メッセージを表示しています。

 最後に、それ以外の物はオプション外の引数として扱うため、配列argvに追加しています(27行目)。

 ここで使用しているwhile文とshiftについては、シェルスクリプトに挑戦しよう(15)の「引数を順番に処理する」を参照してください。また、case文については、シェルスクリプトに挑戦しよう(9)も併せて参照してください。

 配列に値を追加する書き方については、シェルスクリプトに挑戦しよう(16)で扱っています。「配列名+=(値1 値2 値3)」のように書くことで、配列の末尾に値を追加できますが、コマンドラインでは「"My Song.mp3"」のような空白混じりの引数が与えられる可能性があるため、「argv+=("$1")」のように、引用符を付けています。

●「=」区切りのオプション引数に対応する

 続いて、「--aaa」と「--ccc」について、「=」で区切った引数が指定されている場合の処理を追加します。ここでは、22行目と23行目の部分を追加しました。

19 while [ $# -gt 0 ]
20 do
21   case $1 in
22     --aaa=*) flag_a=true; arg_a="${1#--aaa=}";; # --aaa=の後ろをarg_aにセット
23     --ccc=*) flag_c=true; arg_c="${1#--ccc=}";;
24     --aaa) flag_a=true;;
25     --bbb) flag_b=true;;
26     --ccc) flag_c=true;;
27     --) shift; break;; # この後は全て引数
28     -*) opterr=true; echo "不明なオプション: $1";;
29     *)  argv+=("$1");;             # 配列argvに値を追加、空白を含んでいるかもしれないので""を付ける
30   esac
31   shift
32 done
33 
▲長いオプションを処理するシェルスクリプトの例(2)(「shopt2.sh」のcase文を加工)

 「${変数名#文字列}」で、変数の値の先頭から「文字列」に一致する部分を取り除いています。具体的には、「$1」が「--aaa=str1」であれば、「${1#--aaa=}」の値は「str1」となります。

●空白区切りのオプション引数に対応する

 続いて、「--aaa」と「--ccc」について、空白で区切った引数が指定されている場合の処理を追加します。ただし、両方とも次の引数が「-」から始まっている場合は、オプションと見なすようにしました。

 オプション引数として「-」から始まる文字列を指定したい場合は、「--aaa=-test」のように「=」で区切ればよいので、実用上問題はないでしょう()。

【※】逆に、「--ccc」の後ろはどんな文字列でも引数と見なす、としたい場合は、「--」という文字列は除くような措置が必要です。getoptの場合、そのようなケースであれば「引数がない」としてエラーになります。



19 while [ $# -gt 0 ]
20 do
21   case $1 in
22     --aaa=*) flag_a=true; arg_a="${1#--aaa=}";; # --aaa=の後ろをarg_aにセット
23     --ccc=*) flag_c=true; arg_c="${1#--ccc=}";;
24     --aaa) flag_a=true;
25                 if [ "${2::1}" != "-" ]; then # 次の引数が「-」から始まってない場合は
26                   arg_a=$2; shift;            # arg_aにセットして次の引数へ(本文参照)
27                 fi;;
28     --bbb) flag_b=true;;
29     --ccc) flag_c=true;
30                 if [ "${2::1}" != "-" ]; then
31                   arg_c=$2; shift;
32                 fi;;
33     --) shift; break;; # この後は全て引数
34     -*) opterr=true; echo "不明なオプション: $1";;
35     *)  argv+=("$1");;             # 配列argvに値を追加、空白を含んでいるかもしれないので""を付ける
36   esac
37   shift
38 done
39 
▲長いオプションを処理するシェルスクリプトの例(3)(「shopt2.sh」のcase文を加工)

 getoptでは、引数があってもなくてもよいという場合、「--aaa str1」のような指定の場合は「str1はオプション引数ではない」という判定でした()。

【※】引数が必須の場合、そのオプションの次に指定されている文字列は常にオプション引数と見なされるため、「--ccc str1」の「str1」はオプション引数となります。



 これに対し、このシェルスクリプト(shopt2.sh)では「--aaa str1」でも「str1は--aaaのオプション引数である」としています。

 getoptを使った前回のシェルスクリプト(shopt1.sh)の場合、「--aaa str1 --bbb str2 str3」では、「str1」がオプション引数とは見なされませんでした。一方、shopt2.shでは「str1」が「--aaa」のオプション引数と見なされます。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。