今回のテーマは「オプション」です。まずは、外部コマンド「getopt」を使った処理を試してみましょう。シェルスクリプトでオプションを扱いたい場合に、どのような点に注意するか考えてみましょう。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
本連載の第25回、第26回「シェルスクリプトに挑戦しよう(5)、(6)」では、シェルスクリプトで引数を扱う方法を扱いました。今回は「オプション」を扱う方法です。
オプションは、シェルスクリプトやコマンドに渡された引数のうち、「-n」や「--verbose」のように、コマンドの動作を指定する際に使用します。“ハイフン+1文字”、または“ハイフン2つ+文字列”で指定するのが一般的です(※)。「--file=ファイル名」のように、引数(オプション引数)を指定できるオプションもあります。
【※】「ps」コマンドのように「a」や「x」1文字で指定できたり、「find」コマンドの「-file」のようにハイフン1文字で何かを指定できたりするタイプのコマンドもあります。いずれにせよ、コマンドラインから受け取った文字列をどう使うかは、コマンド(の開発者)次第です。
オプションは、引数に先立って指定するのが一般的です。また、一般的には順番を問わずに指定できますが、逆の意味を持つオプションの場合は後に指定したものが優先されるか、あるいは同時に使用できないなどの制限があるかもしれません。
コマンドに対して、ここまではオプション、ここからは引数、と区別するために「--」を使う場合があります。引数として「-」から始まる文字列を使いたい場合などに使用します。
「-n」のような“ハイフン+1文字”のオプションは、「短いオプション」や「ショートオプション」と呼ばれることがあります。
引数付きの場合は、「-f ファイル名」のようにスペースで区切ってオプション引数を書きます。コマンドによっては、「-uユーザー名」のように短いオプションの後に、スペースを入れずに引数を続けて書く場合もあります(※)。
【※】どちらかの一方の書式しか受け付けないコマンドもあります。
短いオプションは、1つにまとめることができます。例えば、「-a」と「-b」というオプションを指定する場合には、「-ab」あるいは「-ba」のように指定することがあります。
複数をまとめて指定したオプションの中に引数を取るオプションがあると、その文字以降がオプションの引数として扱われます。
例えば、「-a」「-b」と「-f ファイル名」というオプションがある場合、「-fab」と指定すると、「ab」が「-f」の引数として扱われることになります。従って、オプションを指定する際は、「-abf ファイル名」のように、引数を取るオプションを最後に書くのが一般的です。もちろん、「-ab -f ファイル名」のように分けて書くこともあるでしょう。
なお、「-f」が引数を取るオプションの場合、「-f -a」と指定すると、「-f」の引数が指定されていないと解釈するか、「-a」を「-f」の引数と取るかも、コマンド次第になります。シェルスクリプトで引数を取るオプションを扱おうとする場合は、その点も考えて方針を定める必要があります。
この他、短いオプションの場合、「-」記号の代わりに「+」記号を使うと意味が逆になるようになっている場合もあります。
“ハイフン2つ+文字列”のオプションは、「長いオプション」や「ロングオプション」と呼ばれています。短いオプションと違い、複数をまとめて指定することはありません。
引数がある場合、「--file=ファイル名」のように「=」(イコール)で区切るか、「--file ファイル名」のようにスペースに続けて引数を指定します。コマンドによっては「=」が必須ということもあります。
オプションの解析を補助してくれるコマンドに、「getopts」コマンド(連載:LinuxコマンドTips 第378回)や「getopt」コマンド(同第379回、同第380回)があります。
getoptsはbashのビルトインコマンドで、短いオプションの解析が可能です。まとめて指定されたオプションにも対応しています。また、引数付きのオプションを扱うことも可能で、この場合、「引数は必須」「オプションの後に続けて、またはスペース区切りで引数を書く」というルールで解析されます。
getoptは外部コマンド(/usr/bin/getopt)で、短いオプションと長いオプションに対応しています。短いオプションはgetoptsと同様、まとめた指定や引数指定が可能です。
長いオプションの場合は、「引数が必須」と「引数は任意」(あってもなくてもよい)という設定が可能です。オプション引数はスペース区切りまたはイコール区切りで指定します。
なお、「引数が任意」の場合、イコール区切りではそのオプションの引数として扱われるのに対し、スペース区切りではオプション引数ではなく、コマンドへの引数として扱われます(連載:LinuxコマンドTips 第380回「シェルスクリプトで応用する(参考)」参照)。
本連載では、まず、getoptを利用したシェルスクリプトを作成し、続いてgetoptなどのコマンドを使わずに、同じようにオプションを処理してみます。
getoptは、「getopt -o 短いオプションに使用したい文字 -l 長いオプションに使用したい文字列 -- 解析したい文字列」のように使用します。
例えば、短いオプションとして「-a」と「-b」と「-c」を使いたい場合は、「-o abc」のように指定します。
引数を取るオプションとしたい場合は、アルファベットの後ろに「:」記号を付けます。「-a」「-b」「-c」のうち、「-a」と「-c」は引数が必須、としたい場合は「-o a:bc:」となります。短いオプションの場合「引数があってもなくてもよい」という設定はできません。
短いオプションが不要な場合は、「-o ""」とします。また、長いオプションが不要な場合、「-l」の指定は不要です。
長いオプションに「--aaa」と「--bbb」と「--ccc」を使いたい場合は、「-l aaa,bbb,ccc」のようにします。
引数を取るオプションとしたい場合は、それぞれの文字列の後ろに「:」記号を付けます。引数があってもなくてもよいという場合は「::」とします。例えば、先ほどの例で「--aaa」は引数があってもなくてもよく、「--ccc」は引数が必須としたい場合は「-l aaa::,bbb,ccc:」とします。
getopt -o abc -l aaa,bbb,ccc -- 解析したい文字列
短いオプションとして「-a」「-b」「-c」、長いオプション「--aaa」「--bbb」「--ccc」を使う
getopt -o "" -l aaa,bbb,ccc -- 解析したい文字列
短いオプションなし、長いオプション「--aaa」「--bbb」「--ccc」を使う
getopt -o "abc:" -l aaa::,bbb,ccc: -- 解析したい文字列
短いオプションとして「-a」「-b」「-c」(引数が必須)、長いオプション「--aaa」(任意で引数を取る)「--bbb」「--ccc」(引数が必須)を使う
(詳細は、連載:LinuxコマンドTips 第379回、第380回を参照)
なお、getoptは長いオプションの省略指定にも対応しており、例えば「--file」「--quiet」「--quote」の3つが設定されている場合、「--f」で「--file」、「--qui」で「--quiet」と見なされます(※)。
【※】getoptには「ハイフン1つでも長いオプションと見なす」というオプション(-a)があります。「getopt -a -o "fi" -l "file"」のように、「-f」と「-i」と「--file」というオプションがあるのにハイフン1つを長いオプション扱いすると、「-fi」が「--file」と見なされる点に注意が必要です。「getopt -a -o "fi" -l "print"」のように、省略指定と短いオプションがかぶらない場合は問題ありませんが、そもそも短いオプションとハイフン1つの長いオプションの混在は避けた方が無難でしょう。
それでは、getoptを使ったシェルスクリプトを試してみましょう。
getoptは「getopt 解析したい内容 -- 解析したい文字列」と指定すると、解析した結果を標準出力に出力します。「解析したい文字列」で対象外のオプションを指定した場合は、エラーメッセージを標準エラー出力に出力します。
シェルスクリプトの中で、シェルスクリプトが受け取った引数を解析するには、「getopt 解析したい内容 -- "$@"」のようにします。「$@」は「引数全て」という意味で、引用符も考慮した状態にしたいため「"$@"」のように指定します。
さらに、「変数=`getopt 解析したい内容 -- "$@"`」とすることで、getoptによる解析結果をいったん変数に保存します。具体的には以下のようにします。
1 #! /bin/bash 2 3 # 短いオプション-a,-b.-cと長いオプション--aaa,--bbb,-cccを使用 4 # -aと-aaa、-bと-bbb、-cと-cccは同じ意味だが、 5 # --aaaは任意で引数を取り、-c、--cccは引数が必須 6 # ――という想定の処理を行うシェルスクリプト(本文も参照のこと) 7 8 # オプション用の前処理 -------------------- 9 10 OPTIONS=`getopt -n $(basename $0) -o abc: -l aaa::,bbb,ccc: -- "$@"` 11 # getoptによる解析結果を変数OPTIONSに保存 12 # getoptのエラーメッセージを表示したくない場合は「-n $(basename $0)」を「-q」にする 13 # echo $OPTIONS # getoptによる解析結果を確認したい場合は行頭の「#」を削除 14
「OPTIONS=`……`」(10行目)で、getoptの解析結果を変数「OPTIONS」に保存しています。
シェルスクリプトに対し、getoptで指定していないオプションを指定した場合は、getoptからのエラーメッセージが表示されます。このときのエラーメッセージを「getopt: エラーメッセージ」ではなく、「シェルスクリプト名: エラーメッセージ」としたいので、「-n 名前」オプションを指定しています。実行時のプログラム名は「$0」で分かるので、「basename $0」として、パス名を取り除いたファイル名のみとします。これでシェルスクリプト名が取得できました。
「$(コマンド)」は「`コマンド`」と同じです。getopt全体を「``」で囲んでいるので、「$()」としました(※)。
【※】「$()」の中に「$()」を書くことも可能です。バッククオート(``)の中にバッククオートを書く場合、内側のバッククオートは「\`〜\`」のように「\」マークを付ける必要があります。
getoptのエラーコードが「0」ではない場合は、使い方を表示して終了します。ここではオプションの種類しか表示していませんが、実際のスクリプトではここで各オプションの意味を記載することになるでしょう。
15 if [ $? -ne 0 ]; then 16 echo "USAGE:" 17 echo " -a,--aaa[=引数] " 18 echo " -b,--bbb " 19 echo " -c 引数,--ccc=引数" 20 exit # 処理を続行させたい場合はこの行を削除 21 fi 22 # getoptのエラーコードが 0 ではない場合(シェルスクリプトに対して間違ったオプションが指定された場合) 23 # 使用方法を表示して終了する 24 # (「$?」は直前に実行したコマンドのエラーコード) 25
次のブロックはコメントの通りです。getoptのエラーコードが「0」以外のときは使用方法を表示して終了します。
26 eval set -- "$OPTIONS" 27 # この後は、getoptの解析結果をシェルスクリプトの引数($1〜)として処理する 28 # -ab が -a -b になるなど、適宜整形・整理されている。 29 30 flag_a=false #変数「flag_a」にコマンド名「false」(本文参照)をセット 31 flag_b=false 32 flag_c=false 33 arg_a= 34 arg_c= 35
続いて、「eval」コマンドで、「set」コマンドを実行しています。これによって、OPTIONSの内容がシェルスクリプトのコマンドライン引数となります。
OPTIONSの内容は、getoptによって、「-ab」のようにまとめて指定されたオプションは「-a」「-b」のようにばらばらになっていますし、「オプション -- 引数」という形に整理されています。
例えば「./shopt1.sh -ab 引数」と実行した場合は、「./shopt1 -a -b -- 引数」が実行されたものとして処理できるようになります。
その後は、変数の初期化です。「flag_a」「flag_b」「flag_c」には、「false」という文字列をセットしています。この後の処理で、「-a」または「--aaa」が指定されていたら「flag_a」を「true」にします。この「true」と「false」はコマンド名なので、全て小文字にしておきます。「arg_a」と「arg_c」は、それぞれの引数用の変数です。
これで準備が整ったので、while文でコマンドライン引数を順番に確認していきます。ここで使用しているwhile文とshiftについては、シェルスクリプトに挑戦しよう(15)の「引数を順番に処理する」を参照してください。また、case文についてはシェルスクリプトに挑戦しよう(9)で扱っています。
36 # オプションの確認 -------------------- 37 38 # 引数を1つずつチェックし、flag_a, b, c および arg_a, cにセット 39 # 「-a」と「-b」で「-ab」のような形になっていることは考慮しなくてよい 40 41 while [ $# -gt 0 ] 42 do 43 case $1 in 44 -a | --aaa) flag_a=true; 45 if [ "${2::1}" != "-" ]; then # 次の引数が「-」から始まっていない場合は 46 arg_a=$2; shift; # arg_aにセットして次の引数へ 47 fi;; 48 -b | --bbb) flag_b=true;; 49 -c | --ccc) flag_c=true; arg_c=$2; shift;; 50 --) shift; break;; # この後は全て引数(getoptによる) 51 esac 52 shift 53 done 54
ここでは、「-a」と「--aaa」のときは、オプション引数が指定されているかどうかを確認しています。次の引数は「$2」で参照できるので、「${2::1}」で$2の1文字目を確認し、「-」から始まっていなければ引数と見なして変数「arg_a」に保存してshiftで1つ進めています。
なお、getoptでは「-a」については「引数なし」としていますが、「-a」の場合も同様のチェックで支障はないと判断して処理を1つにまとめました。
「-c」と「--ccc」の場合は、引数が必須なので、次の引数は必ずオプション引数と見なしています。引数がない場合は、getoptの解析時点でエラーになっているので考慮していません。
なお、「-c」と「--ccc」でも、次の引数が「-」から始まっていた場合はオプション引数ではなく別のオプションであると見なしたい場合は、if文を入れます。また、この場合は「オプション引数がない」と見なしてエラーメッセージを出力したり、「-c」と「--ccc」に続いて「-d」などの無効なオプションがあったりするかもしれないので、その旨の警告を出した方がよいかもしれません。
どのようなオプションが指定されていると解釈したかを出力してみましょう。
変数「flag_a」の値が「true」の場合は、「if $flag_a」で「if true」が実行されることになります。「flag_a」の値が「false」の場合は「if false」です。
「if コマンド」では、コマンドの実行結果が0(成功)のときにthenのブロックが実行されますので、「flag_a」の値が「true」であれば、「echo "-a 指定あり(引数=$arg_a)"」が実行されることになります。
なお、if文についてはシェルスクリプトに挑戦しよう(7)、ifの条件の書き方については、シェルスクリプトに挑戦しよう(8)で解説しています。
55 # 取得したオプションとオプション引数を表示 56 57 if $flag_a; then 58 echo "--aaa 指定あり(引数=$arg_a)" 59 else 60 echo "--aaa 指定なし" 61 fi 62 63 if $flag_b; then 64 echo "--bbb 指定あり" 65 else 66 echo "--bbb 指定なし" 67 fi 68 69 if $flag_c; then 70 echo "--ccc 指定あり(引数=$arg_c)" 71 else 72 echo "--ccc 指定なし" 73 fi 74
最後に、残りの引数を表示します。この部分がシェルスクリプトに対する“引数”(オプション以外の部分)ということになります。
75 # 残りの引数を表示 76 77 while [ $# -gt 0 ] 78 do 79 echo "引数=$1" 80 shift 81 done
先述したように、getoptでは長いオプションの引数が任意の場合、イコール区切りはそのオプションの引数として扱われるのに対し、スペース区切りはオプション引数ではなくコマンドへの引数として扱われます。
従って、「./shopt1.sh --aaa=str1 --bbb」の場合、getoptで解析した結果は「--aaa 'str1' --bbb --」なので、--aaaのオプション引数が「str1」となります。これに対し、「./shopt1.sh --aaa str1 --bbb」とした場合は、getoptの結果が「--aaa '' --bbb -- 'str1'」となるため、--aaaのオプション引数は「なし」ということになります。
この他、省略指定の解釈など、自分の好みとは合っていないかもしれません。次回は、getoptを使わない場合について扱います。
getoptを使わないでオプションを処理する場合、「引数を取るオプションは長いオプションにする」「長いオプションの引数はイコール区切りで指定する」などのルールを決めて、シンプルに処理をすることができます(※)。
【※】そもそも、自作のシェルスクリプトにそこまで複雑なオプション指定が必要であるかどうかもあらためて考える必要があります。
西村 めぐみ(にしむら めぐみ)
もともとはDOSユーザーで「DOS版UNIX-like tools」を愛用。ソフトハウスに勤務し生産管理のパッケージソフトウェアの開発およびサポート業務を担当、その後ライターになる。著書に『図解でわかるLinux』『らぶらぶLinuxシリーズ』『Accessではじめるデータベース超入門[改訂2版]』『macOSコマンド入門』など。地方自治体の在宅就業支援事業にてMicrosoft Officeの教材作成およびeラーニング指導を担当。会社などの“PCお手伝いさん”やピンポイント研修なども行っている。
Copyright © ITmedia, Inc. All Rights Reserved.