Linux のヒント: Bash のテスト関数と比較関数

test と [、[[、((、そして if-then-else の神秘を解く

Bash シェルは、今日の多くの Linux システムや UNIX システムで利用でき、また Linux の一般的なデフォルト・シェルでもあります。Bash には強力なプログラミング機能が備わっており、ファイル・タイプや属性をテストするための広範な種類の関数の他、算術比較や文字列の比較も、ほとんどのプログラミング言語で可能です。さまざまなテストを理解し、またシェルが一部の演算子をシェルのメタキャラクターとして解釈できることを認識しておくことは、シェルのパワー・ユーザーになるための重要なステップです。この記事は developerWorks のチュートリアル「LPI exam 102 prep: Shells, scripting, programming, and compiling」の抜粋として、Bash シェルのテスト演算と比較演算をどのように理解し、使えばよいかについて解説します。

このヒントはシェルのテスト関数と比較関数について説明し、またシェルにプログラミング機能を追加する方法について解説します。皆さんの中には、&& や || 演算子を使った単純なシェル・ロジックを経験している人もいるかもしれません。これらのロジックを利用すると、あるコマンドを、その前のコマンドが正常に終了しているか、あるいはエラーが起きているかに基づいて実行することができます。このヒントでは、そうした基本的な方法を拡張し、より複雑なシェル・プログラミングを行うための方法を学びます。

Ian Shields (ishields@us.ibm.com), Senior programmer, IBM

Ian ShieldsIan Shields は、developerWorks Linux ゾーンの様々な Linux プロジェクトに関わっています。彼はノースキャロライナ州 Research Triangle Park にある IBM のシニア・プログラマーです。1973年にオーストラリアのキャンベラでシステム・エンジニアとして IBM に入社して以来、カナダのモントリオールやノースキャロライナ州 Research Triangle Park で、コミュニケーション・システムやパーベイシブ・コンピューティングに携わってきました。彼はいくつかの特許を保持しています。Australian National University にて純粋数学および哲学で学位を取得し、また North Carolina State University にてコンピューター・サイエンスで修士と博士を取得しています。


developerWorks 貢献著者レベル

2007年 2月 20日

テスト

どのようなプログラミング言語の場合も、変数への値の割り当てやパラメーターの渡し方を学んだ後は、こうした値やパラメーターをテストする必要があります。シェルの場合、テストは戻りステータスを設定しますが、これは他のコマンドが行うことと同じです。実際、test はシェルに組み込まれたコマンドなのです。

test と [

test 組み込みコマンドは、式 expr を評価した結果に基づいて、0 (True) あるいは 1 (False) を返します。あるいは大括弧を使うこともできます。つまり test expr と [ expr ] は等価です。戻り値の検証は、$? を表示することで行うことができます。また戻り値を && や || と組み合わせて使うこともできます。あるいは、後ほど説明する、さまざまな条件構成体を使ってテストすることもできます。

リスト 1. 単純なテスト
[ian@pinguino ~]$ test 3 -gt 4 && echo True || echo false
false
[ian@pinguino ~]$ [ "abc" != "def" ];echo $?
0
[ian@pinguino ~]$ test -d "$HOME" ;echo $?
0

リスト 1 の最初の例では、-gt 演算子が 2 つのリテラル値の間の算術比較を行います。2 番目の例では、もう一方の形式の [ ] で 2 つの文字列が等しいかどうかを比較します。最後の例では、HOME 変数の値がディレクトリーかどうかを、-d 単項演算子を使ってテストしています。

算術値を比較するには、-eq、-ne、-lt、-le、-gt、あるいは -ge のどれか 1 つを使います。これらはそれぞれ、等しい (equal)、等しくない (not equal)、未満 (less than)、以下 (less than or equal)、より大きい (greater than)、以上 (greater than or equal) を意味します。

文字列同士が等しいかどうかの比較、等しくないかどうかの比較、あるいは最初の文字列を 2 番目の文字列の前にソートするか、後にソートするかには、それぞれ演算子 =、!=、<、> を使います。単項演算子 -z はヌル文字列かどうかのテストに使われ、また演算子が -n、あるいは演算子がない場合は、文字列が空でなければ True が返されます。

注意: < 演算子と > 演算子はシェルのリダイレクト用にも使われるため、\< あるいは \> を使ってエスケープする必要があります。リスト 2 は、先ほどとは別の文字列テストを示しています。これらのテストが、皆さんが予想したとおりの結果であることをチェックしてください。

リスト 2. 文字列テスト
[ian@pinguino ~]$ test "abc" = "def" ;echo $?
1
[ian@pinguino ~]$ [ "abc" != "def" ];echo $?
0
[ian@pinguino ~]$ [ "abc" \< "def" ];echo $?
0
[ian@pinguino ~]$ [ "abc" \> "def" ];echo $?
1
[ian@pinguino ~]$ [ "abc" \<"abc" ];echo $?
1
[ian@pinguino ~]$ [ "abc" \> "abc" ];echo $?
1

より一般的なファイル・テストのいくつかを表 1 に示します。テスト対象のファイルが存在し、かつ指定された特性を持つ場合には、結果は True です。

表 1. 一般的なファイル・テスト
演算子特性
-dディレクトリー
-e存在する (-a の場合もあります)
-f通常のファイル
-hシンボリック・リンク (-L の場合もあります)
-p名前付きのパイプ
-rユーザーによる読み取り可能
-s空ではない
-Sソケット
-wユーザーによる書き込み可能
-N最後に読み取られてから修正されている

上記の単項テストの他に、表 2 のようなバイナリー演算子で 2 つのファイルを比較することもできます。

表2. 2つのファイルをテストする
演算子True となる条件
-ntファイル 1 がファイル 2 よりも新しいかどうかをテストする。この比較と次の比較には修正の日付が使われます。
-otファイル 1 がファイル 2 よりも古いかどうかをテストする。
-efファイル 1 がファイル 2 へのハード・リンクかどうかをテストする。

これらの他にも、ファイルのアクセス権をチェックできるテストなどもあります。詳細については bash の man ページを見てください。あるいは、help test を使えば、組み込みのテストに関する簡単な情報を見ることができます。help コマンドは、他の組み込み機能に対しても使うことができます。

-o 演算子を使うと、さまざまなシェル・オプションをテストすることができます。シェル・オプションは set -o option を使って設定でき、設定されていると True (0) が返り、それ以外の場合は False (1) が返ります (リスト 3)。

リスト 3. シェル・オプションをテストする
[ian@pinguino ~]$ set +o nounset
[ian@pinguino ~]$ [ -o nounset ];echo $?
1
[ian@pinguino ~]$ set -u
[ian@pinguino ~]$ test  -o nounset; echo $?
0

最後に、-a オプションと -o オプションを使うと、それぞれ論理 AND と論理 OR を式と組み合わせることができ、また単項 ! 演算子はテストの意味を反転します。括弧を使うと式をグループ化でき、またデフォルトの優先順位を無効にすることができます。ここで、シェルは通常、サブシェルの括弧の間にある式を実行するため、\ (と \) を使って括弧をエスケープするか、あるいはこうした演算子を単一引用符あるいは二重引用符で囲む必要があることを忘れないでください。リスト 4 は、ある式にド・モルガンの法則を適用した例を示しています。

リスト 4. テストを組み合わせ、グループ化する
[ian@pinguino ~]$ test "a" != "$HOME" -a 3 -ge 4 ; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o 3 -lt 4 \) ]; echo $?
1
[ian@pinguino ~]$ [ ! \( "a" = "$HOME" -o '(' 3 -lt 4 ')' ")" ]; echo $?
1

(( と [[

test コマンドは非常に強力ですが、エスケープ処理が必要なこと、文字列比較と算術比較が異なることを考えると、少しばかり面倒です。幸い bash には、これらの他にも、C や C++、Java® などの構文に慣れた人にはもっと自然な、2 つのテスト方法があります。

(( )) 複合コマンドは算術式を評価し、その式の評価結果が 0 であれば終了ステータスを 1 に設定し、式の評価結果が 0 以外の値であれば終了ステータスを 0 に設定します。(( と )) の間の演算子をエスケープする必要はありません。算術演算は整数に対して行われます。0 で割るとエラーになりますが、オーバーフローはエラーになりません。また、C 言語の通常の算術演算、論理演算、ビット単位演算を行うことができます。let コマンドも 1 つ以上の算術式を実行できますが、通常は算術変数に値を割り当てるために使われます。

リスト 5. 算術式を割り当て、テストする
[ian@pinguino ~]$ let x=2 y=2**3 z=y*3;echo $? $x $y $z
0 2 8 24
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 3 8 16
[ian@pinguino ~]$ (( w=(y/x) + ( (~ ++x) & 0x0f ) )); echo $? $x $y $w
0 4 8 13

[[ ]] 複合コマンドの場合も (( )) と同様、ファイル名や文字列のテストを、より自然な構文を使って行うことができます。test コマンド用のテストを、括弧と論理演算子を使って組み合わせることもできます。

リスト 6. [[ 複合コマンドを使う
[ian@pinguino ~]$ [[ ( -d "$HOME" ) && ( -w "$HOME" ) ]] &&  
>  echo "home is a writable directory"
home is a writable directory

[[ 複合コマンドも、= あるいは != 演算子を使うと、文字列に対してパターン・マッチングを行うことができます。このマッチングは、globbing によるワイルドカードのように動作します (リスト 7)。

リスト 7. [[ を使ったワイルドカード・テスト
[ian@pinguino ~]$ [[ "abc def .d,x--" == a[abc]*\ ?d* ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def c" == a[abc]*\ ?d* ]]; echo $?
1
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* ]]; echo $?
1

[[ 複合コマンドの中で、算術テストを行うことさえできます。ただし注意が必要です。< 演算子や > 演算子は、[[ 複合コマンドの中にないと、オペランドを文字列として比較し、オペランドの順序を現在の照合シーケンスでテストします。これらを例で示したものがリスト 8 です。

リスト 8. [[ に算術テストを含める
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || (( 3 > 2 )) ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 -gt 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || 3 > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a > 2 ]]; echo $?
0
[ian@pinguino ~]$ [[ "abc def d,x" == a[abc]*\ ?d* || a -gt 2 ]]; echo $?
-bash: a: unbound variable

条件分岐

上記のテストや && や || などの制御演算子でも膨大な量のプログラミングを実現できますが、bash には、もっとおなじみの「if、then、else」や case 構成体が含まれています。これらを学んだ後にはループ構成体について学ぶので、皆さんのツールボックスは大きくふくらむはずです。

If、then、else 文

bash の if コマンドは複合コマンドです。このコマンドはテストあるいはコマンドの戻り値をテストし ($?)、そしてテスト結果が True (0) か False (not 0) かによって分岐します。上記のさまざまなテストでは、0 か 1 の値しか返されませんでしたが、コマンドが他の値を返す場合もあります。これらについての詳細を学ぶには、チュートリアル「LPI exam 102 prep: Shells, scripting, programming, and compiling」を参照してください。

bash の if コマンドには、テストあるいはコマンドが 0 を返した場合に実行すべきコマンドのリストを含む then 節と、1 つ以上のオプション elif 節 (それぞれの節が、追加のテストと、そのテストに関連付けられたコマンドをリストした then 節を持っています)、オプションとしての最後の else 節 (オリジナルのテストでも elif 節で使われたどのテストでも真でなかった場合に実行すべきコマンドのリストがあります)、そして構成体の終わりを示す最後の fi があります。

これまでに学んだことを使うと、算術式を評価する単純な計算プログラムを作ることができます (リスト 9)。

リスト 9. if、then、else を使って式を評価する
[ian@pinguino ~]$ function mycalc ()
> {
>   local x
>   if [ $# -lt 1 ]; then
>     echo "This function evaluates arithmetic for you if you give it some"
>   elif (( $* )); then
>     let x="$*"
>     echo "$* = $x"
>   else
>     echo "$* = 0 or is not an arithmetic expression"
>   fi
> }
[ian@pinguino ~]$ mycalc 3 + 4
3 + 4 = 7
[ian@pinguino ~]$ mycalc 3 + 4**3
3 + 4**3 = 67
[ian@pinguino ~]$ mycalc 3 + (4**3 /2)
-bash: syntax error near unexpected token `('
[ian@pinguino ~]$ mycalc 3 + "(4**3 /2)"
3 + (4**3 /2) = 35
[ian@pinguino ~]$ mycalc xyz
xyz = 0 or is not an arithmetic expression
[ian@pinguino ~]$ mycalc xyz + 3 + "(4**3 /2)" + abc
xyz + 3 + (4**3 /2) + abc = 35

この計算プログラムは local 文を利用することによって、x をmycalc 関数のスコープ内でしか利用できないローカル変数として宣言しています。let 関数には、let 関数と密接に関係する declare 関数と同様に、いくつかのオプションがあります。これに関する詳細の情報は、bash の man ページをチェックするか、あるいは let の helpを使って調べてください。

リスト 9 を見るとわかるように、式がシェルのメタキャラクター ( ( や )、*、>、< など) を使っている場合には、それらの式を必ず適切にエスケープする必要があります。とはいえ、シェルが算術演算をしてくれるので、この計算プログラムは算術演算の評価用に非常に便利です。

皆さんの中には、リスト 9 の else 節と最後の 2 つの例に気付いた人がいるかもしれません。これを見るとわかるように、xyz を mycalc に渡すのはエラーではありませんが、これは 0 と評価されます。この関数は、最後の使い方の例では文字の値を識別してユーザーに警告するほど賢くはありません。[[ ! ("$*" == *[a-zA-Z]* ]] (あるいはロケールにあった適切な形式) のような文字列パターン・マッチング・テストを使うことで、アルファベット文字を含むすべての式を削除することもできますが、そうすると入力に 16 進表記を使えなくなります。(皆さんは 15 を表現するために16 進表記の 0x0f を使うかもしれません)。実際シェルは、(base#value 表記を使うことで) 64 までの基数を許しています。そのため、入力には任意のアルファベット文字と、_ と @ を問題なく使うことができます。8 進と 16 進では、8 進の場合は先頭 0、16 進の場合は 0x あるいは 0X として、通常の表記を使います。リスト 10 はこうした例を示したものです。

リスト 10. さまざまな基数で計算する
[ian@pinguino ~]$ mycalc 015
015 = 13
[ian@pinguino ~]$ mycalc 0xff
0xff = 255
[ian@pinguino ~]$ mycalc 29#37
29#37 = 94
[ian@pinguino ~]$ mycalc 64#1az
64#1az = 4771
[ian@pinguino ~]$ mycalc 64#1azA
64#1azA = 305380
[ian@pinguino ~]$ mycalc 64#1azA_@
64#1azA_@ = 1250840574
[ian@pinguino ~]$ mycalc 64#1az*64**3 + 64#A_@
64#1az*64**3 + 64#A_@ = 1250840574

これら以外の入力操作は、このヒントでは想定していません。そのため、この計算プログラムを使う場合には注意が必要です。

elif 文は非常に便利です。elif 文を使うと簡単にインデントできるので、スクリプトを書く際に便利です。mycalc 関数に type コマンドを使うと、驚くような出力が得られます (リスト 11)。

リスト 11. type mycalc
[ian@pinguino ~]$ type mycalc
mycalc is a function
mycalc ()
{
    local x;
    if [ $# -lt 1 ]; then
        echo "This function evaluates arithmetic for you if you give it some";
    else
        if (( $* )); then
            let x="$*";
            echo "$* = $x";
        else
            echo "$* = 0 or is not an arithmetic expression";
        fi;
    fi
}

もちろん、echo コマンドと $(( expression )) を使って単純にシェルで算術演算を行うこともできます (リスト 12)。皆さんはこうした方法での関数やテストを習っていないかもしれませんが、(( expression )) や [[ expression ]] の中では、シェルはメタキャラクター (* など) を通常の役割としては解釈しないことに十分注意してください。

リスト 12. echo と $(( )) を使ってシェルの中で直接計算する
[ian@pinguino ~]$echo $((3 + (4**3 /2)))
35

詳しく学ぶには

Linux での Bash スクリプトについて詳しく知りたい方は、チュートリアル「LPI exam 102 prep: Shells, scripting, programming, and compiling」を読んでください (この記事はこのチュートリアルから抜粋したものです)。また、この記事を評価することも忘れないでください。

参考文献

学ぶために

製品や技術を入手するために

  • IBM trial software は developerWorks から直接ダウンロードすることができます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, AIX and UNIX, Open source
ArticleID=249426
ArticleTitle=Linux のヒント: Bash のテスト関数と比較関数
publish-date=02202007