前回に引き続き、今回のテーマも「オプション」です。今回は「getopt」などを使わずに処理します。シェルスクリプトでオプションを扱いたい場合、どのような点に注意するかも併せて考えていきましょう。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
まずは、「長いオプション」について扱います。変数名などは、前回作成したシェルスクリプトを使っています。
この時点では、まだ、引数については受け皿とする変数を用意しているのみで、具体的な処理は行っていません。また、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
新しい変数として、オプション指定にエラーがあるかどうかを保存する「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
「${変数名#文字列}」で、変数の値の先頭から「文字列」に一致する部分を取り除いています。具体的には、「$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
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」のオプション引数と見なされます。
しかし、今回のshopt2.shでは「--aaa str1 str2 str3」という指定があった場合、「str1」はオプション引数、「str2」「str3」はオプション外の引数となります。str1もオプション引数とは見なされたくない場合、「--aaa -- str1 str2 str3」のように「--」で区切る必要があります。
そもそも、長いオプションに対する引数は必ず「=」を使って指定すること、と決めておけばシンプルです。シェルスクリプトも読みやすくなるでしょう。
shopt2.shの場合 | shopt1.sh(getopt)の場合 | |||
---|---|---|---|---|
コマンドライン引数 | --aaaの オプション引数 |
オプション外の 引数 |
--aaaの オプション引数 |
オプション外の 引数 |
--aaa=str1 str2 str3 | str1 | str2、str3 | str1 | str2、str3 |
--aaa=str1 --bbb str2 str3 | str1 | str2、str3 | str1 | str2、str3 |
--aaa=str1 str2 --bbb str3 | str1 | str2、str3 | str1 | str2、str3 |
--aaa str1 str2 str3 | str1 | str2、str3 | なし | str1、str2、str3 |
--aaa str1 --bbb str2 str3 | str1 | str2、str3 | なし | str1、str2、str3 |
--aaa str1 str2 --bbb str3 | str1 | str2、str3 | なし | str1、str2、str3 |
▲【参考】引数がどう扱われるか(---aaaが「引数が必須」となっている場合、shopt1.shでもshopt2.shと同じ結果となる) |
続いて、エラーチェックです。「--ccc」にオプション引数が指定されていなかった場合は、エラーとしています。前回同様、ここではオプションの種類しか表示していませんが、実際のスクリプトでは、ここで各オプションの意味を記載することになるでしょう。
40 # エラーチェック 41 42 if $flag_c; then 43 if [ "$arg_c" == "" ]; then 44 echo "--ccc には引数が必要です" 45 opterr=true 46 fi 47 fi 48 49 if $opterr; then 50 echo "USAGE:" 51 echo " --aaa[=引数]" 52 echo " --bbb " 53 echo " --ccc=引数 " 54 exit # 処理を続行させたい場合はこの行を削除 55 fi 56
続いて、残りの引数、つまり「--」より後に指定されていた引数を、配列argvに追加しています。
57 58 # 残りの引数を保存("--"以降) 59 60 while [ $# -gt 0 ] 61 do 62 argv+=("$1") 63 shift 64 done
最後に、結果を表示します。ここで使用しているif文については、第39回を参照してください。
65 # 取得したオプションとオプション引数を表示 66 67 if $flag_a; then 68 echo "--aaa 指定あり(引数=$arg_a)" 69 else 70 echo "--aaa 指定なし" 71 fi 72 73 if $flag_b; then 74 echo "--bbb 指定あり" 75 else 76 echo "--bbb 指定なし" 77 fi 78 79 if $flag_c; then 80 echo "--ccc 指定あり(引数=$arg_c)" 81 else 82 echo "--ccc 指定なし" 83 fi 84 85 # 残りの引数を表示 86 87 for ((i = 0; i < ${#argv[@]}; i++)) #空白を含む値があるかもしれないので「for 変数 in 配列」は使用しない 88 do 89 echo "引数=${argv[$i]}" 90 done
今回は長いオプションについて扱いました。短いオプションについても、「-a」と「-b」を「-ab」や「-ba」のようにまとめずに指定するのであれば、同じように処理できます。ただし、短いオプションに対する引数の場合「=」を使うことはあまりないので、その辺について「どこまで対応したいか」「どのように対応したいか」は考える必要があるでしょう。
次回は短いオプションを扱います。
西村 めぐみ(にしむら めぐみ)
もともとはDOSユーザーで「DOS版UNIX-like tools」を愛用。ソフトハウスに勤務し生産管理のパッケージソフトウェアの開発およびサポート業務を担当、その後ライターになる。著書に『図解でわかるLinux』『らぶらぶLinuxシリーズ』『Accessではじめるデータベース超入門[改訂2版]』『macOSコマンド入門』など。地方自治体の在宅就業支援事業にてMicrosoft Officeの教材作成およびeラーニング指導を担当。会社などの“PCお手伝いさん”やピンポイント研修なども行っている。
Copyright © ITmedia, Inc. All Rights Reserved.