Bシェル(bash/dash/zsh)のif文について

Last modified: 2021-12-23

bashやzshの対話シェルやシェルスクリプトの中で条件分岐を行う際にはif文を使用する。

一般的に、実行ファイルが正常に終了するとシェルは0という値を終了ステータス(exit status)として受け取り、それ以外のときに0以外の値1を受け取る2が、ifの後ろに記述したコマンドの終了ステータスが0かどうかによって処理を分けることができる。

数値や文字列の比較、ファイルやディレクトリの存在や種類のチェックなどによって分岐を行うにはtestコマンドを用いる。

if文の例

下の例を実行すると質問ダイアログが出て

“OK” を選択すると、Zenityの仕様により0が返されて条件を満たすため、“OK” のダイアログが出る。 “キャンセル” を選択すると、1が返されて条件を満たさないため、“キャンセル” のダイアログが出る。

この例はzenityパッケージがインストールされていない環境では正常に動作しない。GNOMEのGUI環境であれば既にインストールされている。

単一行

$ if zenity --question; then zenity --info --text "OK"; else zenity --info --text "Cancel"; fi

複数行

各行の最後でEnterを押す。fiの行でEnterが押されるまでは入力の途中として扱われ、処理は実行されない。

$ if zenity --question
> then
> zenity --info --text "OK"
> else
> zenity --info --text "Cancel"
> fi

zshでは>の部分がthen>else>といった表示になり、見やすくなっている。

シェルスクリプトとして書く場合は字下げ(インデント)をして記述すると見やすい。

#! /bin/sh
if zenity --question; then
    zenity --info --text "OK"
else
    zenity --info --text "Cancel"
fi

if文の形

基本的な書式

以下を;または改行によって区切る。

  • if [コマンド...]
  • then [コマンドの終了ステータスが0のときの処理]
  • else [コマンドの終了ステータスが0以外のときの処理] (処理がない場合は省略可)
  • fi

ifを記述した後はifに対応したfiで閉じる必要がある。

“コマンドの終了ステータスが0のときのみ、then以下の処理を実行する” というのが基本。

if [コマンド]; then
    [終了ステータスが0のときの処理]
fi
(trueコマンドは0を返すので、then以下が実行される)
$ if true; then echo "TRUE"; fi
TRUE

(falseコマンドは0以外を返すので、何もしない)
$ if false; then echo "TRUE"; fi
(何も表示されない)

trueは必ず0falseは必ず1を返すコマンド。

分岐条件を逆にする

!をコマンドの手前に付けると条件が逆になり、終了ステータスが0以外になったときに条件が満たされる。

if ! [コマンド]; then
    [終了ステータスが0以外のときの処理]
fi
(trueコマンドは0を返し、条件が逆転しているので、何もしない)
$ if ! true; then echo "TRUE"; fi
(何も表示されない)

(falseコマンドは0以外を返し、条件が逆転しているので、else以下が実行される)
$ if ! false; then echo "TRUE"; fi
TRUE

条件を満たす場合と満たさない場合の両方の処理を記述

条件が満たされた場合と満たされなかった場合の両方の処理を記述するには、後者をelse以下に記述する。

if [コマンド]; then
    [終了ステータスが0のときの処理]
else
    [終了ステータスが0以外のときの処理]
fi
(trueコマンドは0を返すので、then以下が実行される)
$ if true; then echo "TRUE"; else echo "FALSE"; fi
TRUE

(falseコマンドは0以外を返すので、else以下が実行される)
$ if false; then echo "TRUE"; else echo "FALSE"; fi
FALSE

!をコマンドの手前に付けると条件が逆になるのは同様。

if ! [コマンド]; then
    [終了ステータスが0以外のときの処理]
else
    [終了ステータスが0のときの処理]
fi
(trueコマンドは0を返し、条件が逆転しているので、else以下が実行される)
$ if ! true; then echo "TRUE"; else echo "FALSE"; fi
FALSE

(falseコマンドは0以外を返し、条件が逆転しているので、then以下が実行される)
$ if ! false; then echo "TRUE"; else echo "FALSE"; fi
TRUE

分岐の中に分岐を記述する

処理部分の中にif文を入れることもできるが、それに対応したfiで閉じることが必要。

if [コマンド1]; then
    if [コマンド2]; then
        [コマンド1,2の終了ステータスが0のときの処理]
    fi
fi
(trueコマンドは0を返すので中のif文へ進み、trueコマンドは0を返すので、中のif文におけるthen以下が実行される)
$ if true; then if true; then echo "TRUE"; fi; fi
TRUE

(trueコマンドは0を返すので中のif文へ進み、falseコマンドは0以外を返すので、何もしない)
$ if true; then if false; then echo "TRUE"; fi; fi
(何も表示されない)

多数の分岐

if - elif - elif - ... - else - fiで分岐されたものは、先頭のif文から順に評価して、最初にコマンドの終了ステータスが0になったところの処理が実行され、どれも満たさないときにelse以下の処理が実行される。elseの部分を省略した場合、どれも満たさないときにはどの処理も実行されない。

2つ目以降はelifという表記をすることに注意。

!をコマンドの手前に付けた場合、これを付けたif/elif文のみ判定が逆になる。

if [コマンド1]; then
    [コマンド1の終了ステータスが0のときの処理]
elif [コマンド2]; then
    [コマンド1の終了ステータスが0以外 かつ コマンド2の終了ステータスが0 のときの処理]
elif [コマンド3]; then
    [コマンド1,2の終了ステータスが0以外 かつ コマンド3の終了ステータスが0 のときの処理]
else
    [それ以外のときの処理]
fi
(1番目の終了ステータスが0)
$ if true; then echo "1"; elif true; then echo "2"; elif true; then echo "3"; else echo "4"; fi
1

(2番目の終了ステータスが0)
$ if false; then echo "1"; elif true; then echo "2"; elif true; then echo "3"; else echo "4"; fi
2

(3番目の終了ステータスが0)
$ if false; then echo "1"; elif false; then echo "2"; elif true; then echo "3"; else echo "4"; fi
3

(4番目の終了ステータスが0)
$ if false; then echo "1"; elif false; then echo "2"; elif false; then echo "3"; else echo "4"; fi
4

(2番目の終了ステータスが条件逆転により0)
$ if false; then echo "1"; elif ! false; then echo "2"; elif false; then echo "3"; else echo "4"; fi
2

“else if” と書くとおかしくなる?

else ifという書き方があった場合、ある条件を満たさないときの処理の先頭にif文による分岐があるものとみなされる。

例えば

if [コマンド1]; then
    [コマンド1の終了ステータスが0のときの処理]
else if [コマンド2]; then
    [コマンド1の終了ステータスが0以外 かつ コマンド2の終了ステータスが0 のときの処理]
fi

というのは文法違反で、

if [コマンド1]; then
    [コマンド1の終了ステータスが0のときの処理]
else
    if [コマンド2]; then
        [コマンド1の終了ステータスが0以外 かつ コマンド2の終了ステータスが0 のときの処理]
    fi
fi

とするのが正しいが、

if [コマンド1]; then
    [コマンド1の終了ステータスが0のときの処理]
elif [コマンド2]; then
    [コマンド1の終了ステータスが0以外 かつ コマンド2の終了ステータスが0 のときの処理]
fi

という意図で間違えた書き方をしていたというケース3もある。

  • 使用したバージョン
    • bash 3.2_p17(3.2_p17-r1)
    • zsh 4.3.4(4.3.4-r1)

  1. 具体的な値は決まっておらず、実行しているプログラムの中で指定されている値が返る ↩︎

  2. 1つ前に実行したコマンドの終了ステータスは読み取り専用特殊パラメータ?に入り、echo ${?}のようにすると表示できる ↩︎

  3. elifと書くべきところをelse ifと書いた場合 ↩︎