シェルスクリプトに挑戦しよう(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」のオプション引数と見なされます。

 しかし、今回の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 
▲長いオプションを処理するシェルスクリプトの例(4)

 続いて、残りの引数、つまり「--」より後に指定されていた引数を、配列argvに追加しています。

57 
58 # 残りの引数を保存("--"以降)
59 
60 while [ $# -gt 0 ]
61 do
62   argv+=("$1")
63   shift
64 done
▲長いオプションを処理するシェルスクリプトの例(5)

 最後に、結果を表示します。ここで使用している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
▲長いオプションを処理するシェルスクリプトの例(6)

 今回は長いオプションについて扱いました。短いオプションについても、「-a」と「-b」を「-ab」や「-ba」のようにまとめずに指定するのであれば、同じように処理できます。ただし、短いオプションに対する引数の場合「=」を使うことはあまりないので、その辺について「どこまで対応したいか」「どのように対応したいか」は考える必要があるでしょう。

 次回は短いオプションを扱います。

筆者紹介

西村 めぐみ(にしむら めぐみ)

もともとはDOSユーザーで「DOS版UNIX-like tools」を愛用。ソフトハウスに勤務し生産管理のパッケージソフトウェアの開発およびサポート業務を担当、その後ライターになる。著書に『図解でわかるLinux』『らぶらぶLinuxシリーズ』『Accessではじめるデータベース超入門[改訂2版]』『macOSコマンド入門』など。地方自治体の在宅就業支援事業にてMicrosoft Officeの教材作成およびeラーニング指導を担当。会社などの“PCお手伝いさん”やピンポイント研修なども行っている。


Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

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

メールマガジン登録

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