シェルスクリプトに挑戦しよう(8)条件の書き方“応用力”をつけるためのLinux再入門(28)

シェルスクリプトの中では、さまざまな条件を指定して使います。今回は、if文での書き方を中心に「条件」の書き方を解説します。また、スクリプトで問題になりやすい「変数の引用符」についても取り上げます。

» 2018年10月31日 05時00分 公開
[西村めぐみ@IT]
「“応用力”をつけるためのLinux再入門」のインデックス

“応用力”をつけるためのLinux再入門

条件の書き方

 シェルスクリプトの中では、さまざまな「条件」を指定して使います。この“条件”とは、基本的に“コマンドの実行結果”になります。

 例えば、「if コマンドA then コマンドB else コマンドC fi」のような場合は、コマンドAの実行結果がTRUE(成功)だったらコマンドBが実行され、FALSE(失敗)だったらコマンドCが実行されます。

●条件判定に使われるtestコマンド

 ファイルの有無や属性、変数の内容などによって判定したい場合は、「test」コマンドを使います。

 例えば、「ファイルabcが存在するかどうか」は「test -f "abc"」となります。この場合、abcという名前のファイルが存在した場合、結果はTRUEになります。同様に、「変数VARの内容がxyzかどうか」は「test "$VAR" = "xyz"」で調べることができます。ここで使用している「-f」や「"abc"」、あるいは「=」は、それぞれがtestコマンドの引数なので、空白で区切る必要があります。

●testコマンドを試すには?

 testコマンドの実行結果はTRUEかFALSEという値、具体的には「0」か「1」という数値がシェルに返されるだけなので、コマンドラインで実行しても結果を見ることができません。

 if文で使うと「0」はTRUE、それ以外はFALSEとして扱われるので、スクリプトの中で試すことができます(※1)。

【※1】シェルスクリプトを作らずに試してみたい場合は、連載『Linux基本コマンドTip』の「test」コマンド(基礎編)を参照。


 例えば、以下のスクリプトでは、変数LANG(使用する言語が設定されている環境変数)が「C」だった場合はTRUE、それ以外の場合はFALSEに続けて、LANGの内容を出力しています。

  1. #! /bin/bash
  2. if test "$LANG" = "C"
  3. then
  4. echo "TRUE"
  5. else
  6. echo "FALSE $LANG"
  7. fi
▲testコマンドの動作を試す(testlang)
  1. $ chmod +x testlang
  2. $ ./testlang
  3. FALSE ja_JP.utf8 #← LANGの内容がCではないのでFALSEが表示された
  4. $ LANG=C ./testlang #環境変数LANGCをセットしてtestlangを実行
  5. TRUE #← LANGCなのでTRUEが表示された
▲実行結果(testlang)

testコマンドの省略表記

 testコマンドは、省略して「[ ]」記号で表現できます。これは、testコマンドと同等の働きを持つ「[」コマンドで実現されています。testコマンドと「[」コマンドの違いは、「[」コマンドは最後の引数として「]」が必要という点です。

 例えば、「test -f abc」ならば「[ -f abc ]」、「test "$LANG" = "C"」ならば「[ "$LANG" = "C" ]」となります。

 「-f」や「=」などが、それぞれ「[」コマンドの引数であるという点は、testコマンドと同じです。従って、条件を「[ ]」で書く場合も、空白は省略できません。

 以下は、先ほどの「testlang」スクリプトを「[ ]」記号で書き換えています。実行結果は同じです。

  1. #! /bin/bash
  2. if [ "$LANG" = "C" ] #← if test $LANG = C」を「[」コマンドで書き換えた
  3. then
  4. echo "TRUE"
  5. else
  6. echo "FALSE $LANG"
  7. fi
▲testコマンドを「[ ]」で表記する(testlangを加工)

条件で使用できる“式”

 「[ -f abc ]」のような書き方は、一般に「条件式」と呼ばれています。主な条件式には以下のようなものがあります。全てtestコマンドのオプションなので、「man」コマンドを使い、「man test」で詳細を確認できます。

●ファイルの判定
TRUEになる条件
-e ファイル名 ファイルが存在するとき
-f ファイル名 ファイルが通常のファイルのとき
-d ファイル名 ディレクトリのとき
-s ファイル名 ファイルの長さが0ではない(ファイルが空ではない)とき
-L ファイル名 ファイルがシンボリックリンクのとき
-h ファイル名 ファイルがシンボリックリンクのとき(「-L」と同じ)
ファイル1 -ef ファイル2 ファイル1がファイル2のハードリンクのとき

●ファイル属性の判定
TRUEになる条件
-r ファイル名 ファイルが存在し、読み出しの権限がユーザーにあるとき
-w ファイル名 ファイルが存在し、書き込み権限がユーザーにあるとき
-x ファイル名 ファイルが存在し、ファイルの実行権限がユーザーにあるとき
-O ファイル名 ファイルの実体の所有者が実効ユーザーIDと同じとき(※2
-G ファイル名 ファイルの実体の所属グループが実効グループIDと同じとき
-u ファイル名 ファイルの実体にsetuidビットが立っているとき
-g ファイル名 ファイルの実体にsetgidビットが立っているとき
-k ファイル名 ファイルの実体にstickyビットが立っているとき
ファイル1 -nt ファイル2 ファイル1の修正時刻がファイル2の修正時刻より新しいとき
ファイル1 -ot ファイル2 ファイル1の修正時刻がファイル2の修正時刻より古いとき

【※2】「実効ユーザーID(euid)」は、プログラムが動作するときの権限。プログラムを起動したユーザーのID(実ユーザーID、ruid)と等しいことが多い。


●標準入出力の判定
TRUEになる条件
-t 0 標準入力が端末
-t 1 標準出力が端末
-t 2 標準エラー出力が端末
-t 数値 数値番目のファイルディスクリプターが端末

●文字列の判定
TRUEになる条件
文字列1 = 文字列2 文字列1と文字列2が等しいとき
文字列1 != 文字列2 文字列1と文字列2が等しくないとき
-z 文字列1 文字列1の長さが0のとき
-n 文字列1 文字列1の長さが0ではないとき

●整数の判定
TRUEになる条件
数値1 -eq 数値2 数値1と数値2が等しいとき(equal)
数値1 -ne 数値2 数値1と数値2が等しくないとき(not equal)
数値1 -gt 数値2 数値1が数値2より大きいとき(greater than)
数値1 -ge 数値2 数値1が数値2より大きいか等しいとき(greater or equal)
数値1 -lt 数値2 数値1が数値2より小さいとき(lesser than)
数値1 -le 数値2 数値1が数値2より小さいか等しいとき(lesser or equal))

●その他(条件の組み合わせなど)
TRUEになる条件
-o オプション シェルオプションが定義されていた(※3
-v 変数名 変数が定義されていた(※3
! 条件式 条件式が偽のとき(not)
条件式1 -a 条件式2 条件式1と条件式2がどちらも真のとき(and)
条件式1 -o 条件式2 条件式1と条件式2のどちらかが真のとき(or)
( 条件式 ) ()の中を優先して評価する(※4
TRUE 常に真
FALSE 常に偽

【※3】「-o」と「-v」は「/bin/test」では使用できない(-vはbashバージョン4.2以降)。シェルオプションについては連載『Linux基本コマンドTips』の「set」コマンドを参照。


【※4】「(」「)」を使用する際はバックスラッシュなどでエスケープする必要がある。

変数と引用符

 シェルスクリプトの場合、文字列の引用符("")は必須ではないため、変数を参照する際の引用符も必須ではありません。

 しかし、原則としては付けておく方が安全です。変数を条件式の中で使う場合、引用符がないとエラーになって実行できなくなるケースがあるためです。

 次の例を見てみましょう。この「quottest1」スクリプトは、変数TESTSTRに1つ目の引数をセットし、TESTSCRの値がabcだったら「OK」、それ以外の時は「NO」と表示する、という内容です。

  1. #! /bin/bash
  2. TESTSTR=$1
  3. if [ $TESTSTR = "abc" ]
  4. then
  5. echo "OK"
  6. else
  7. echo "NG"
  8. fi
▲引用符の有無で処理の内容が変わる例(quottest1)

 ここでは、“変数TESTSTRの値がabcだったら~”というつもりで「if [ $TESTSTR = "abc" ]」と書いています。しかし、TESTSTRが定義されていない場合は実行時に「if [ = "abc" ]」となり、文法上の誤りとなり実行できません。

  1. $ chmod +x quottest1
  2. $ ./quottest1 abc
  3. OK #← 1つ目の引数がabcなので「OK」と表示された
  4. $ ./quottest1 aaa
  5. NG #← 1つ目の引数がabcではないので「NG」と表示された
  6. $ ./quottest1
  7. ./quottest1: line 3: [: =: unary operator expected
  8. NG
  9. #↑ 引数がないので変数TESTSTRが定義されておらず、ifの行がエラーになっている
▲実行結果(quottest1)

 これに対し、「if [ "$TESTSTR" = "abc" ]」としていれば、変数TESTSTRが定義されていない場合は「if [ "" = "abc" ]」となり、文法上の誤りはなくなります。

  1. #! /bin/bash
  2. TESTSTR=$1
  3. if [ "$TESTSTR" = "abc" ]
  4. then
  5. echo "OK"
  6. else
  7. echo "NG"
  8. fi
▲引用符の有無で処理の内容が変わる例(quottest1を加工)
  1. $ ./quottest1 abc
  2. OK #← 1つ目の引数がabcなので「OK」と表示された
  3. $ ./quottest1 aaa
  4. NG #← 1つ目の引数がabcではないので「NG」と表示された
  5. $ ./quottest1
  6. NG #← 引数がない=1つ目の引数がabcではないので「NG」と表示された
▲実行結果(quottest1)

●空白のあるファイル名の場合

 ファイル名が入っている想定の変数に、空白入りのファイル名が入っているような場合は、引用符がないとうまく実行できません。

 例えば、次の例を見てみましょう。変数FILENAMEにセットされている名前のファイルが存在しているかどうかを調べる、というスクリプトです。

 使用イメージとしては、変数FILENAMEにセットするファイル名はキーボードから入力したり、ファイルから入力したりしてチェックするというものですが、ここでは、実験のために変数FILENAMEには直接「01 My Song.mp3」という文字列をセットしています。

 そして、$FILENAMEの引用符の有無で実行結果がどのように変わるかを見ています。1つ目のif文は引用符なし、2つ目のif文は引用符ありです。

  1. #! /bin/bash
  2. FILENAME="01 My Song.mp3"
  3. if [ -f $FILENAME ] #← 1つ目のif
  4. then
  5. echo "$FILENAME is a regular file"
  6. else
  7. echo "$FILENAME is not a regular file"
  8. fi
  9. if [ -f "$FILENAME" ] #← 2つ目のif
  10. then
  11. echo "$FILENAME is a regular file"
  12. else
  13. echo "$FILENAME is not a regular file"
  14. fi
▲ファイル名に空白が入っている場合(quottest2)

 1つ目のif文は、実行時は「[ -f $FILENAME ]」が「[ -f 01 My Song.mp3 ]」となります。これは、「[ -f」に対して「01」「My」「Song.mp3」の3つの引数を渡したことになるのでエラーとなります。

 これに対し、2つ目のif文は「[ -f "$FILENAME" ]」が「[ -f "01 My Song.mp3" ]」となるので、正しくファイルの存在をテストできます。

 実際に実行すると以下のような結果となります。ここでは、動作テスト用に「touch」コマンドであらかじめ「01 My Song.mp3」という名前のファイルを作成しています。

  1. $ touch "01 My Song.mp3" #← 動作テスト用にtouchコマンドでファイルを作成
  2. $ chmod +x quottest2
  3. $ ./quottest2
  4. ./quottest2: line 3: [: too many argument #← 1つ目のif文がエラーになった
  5. 01 My Song.mp3 is not a regular file #← エラーのためelse以下が実行されている
  6. 01 My Song.mp3 is a regular file #← 2つ目のif文の実行結果
▲実行結果(quottest2)

 引数なしで実行すると1つ目のif文がエラーになるため、エラーメッセージが表示されています。さらに、ifの判定もFALSEとなっているため、「~is not a regular file」というメッセージが表示されています。

 これに対し2つ目のif文は正しく実行されており、「~is a regular file」というメッセージが表示されています。

●コマンドラインの場合

 コマンドラインでも事情は同じです。先ほどの例と同じように、環境変数に空白入りのファイル名をセットして、「ls」コマンドの引数として実行してみましょう。

 変数FILENAMEに「01 My Song.mp3」をセットし、(1)「ls $FILENAME」のように引用符なしで実行した場合と、(2)「ls "$FILENAME"」と引用符ありで実行した場合を比較しています。

  1. $ FILENAME="01 My Song.mp3"
  2. $ ls $FILENAME #← 1)引用符なしで実行
  3. ls: 01: No such file or directory
  4. ls: My: No such file or directory
  5. ls: Song.mp3: No such file or directory
  6. lsコマンドは「01」と「My」と「Song.mp3」という3つの引数を受け取っている)
  7. $ ls "$FILENAME" #← 2)引用符ありで指定
  8. 01 My Song.mp3
  9. lsコマンドは「01 My Song.mp3」を受け取った)
▲コマンドラインで試してみた例

筆者紹介

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

PC-9801NからのDOSユーザー。PC-486DX時代にDOS版UNIX-like toolsを経てLinuxへ。1992年より生産管理のパッケージソフトウェアの開発およびサポート業務を担当。著書に『図解でわかるLinux』『らぶらぶLinuxシリーズ』『Accessではじめるデータベース超入門[改訂2版]』『macOSコマンド入門』など。2011年より、地方自治体の在宅就業支援事業にてPC基礎およびMicrosoft Office関連の教材作成およびeラーニング指導を担当。

Copyright © ITmedia, Inc. All Rights Reserved.

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

Linux �� OSS 險倅コ九Λ繝ウ繧ュ繝ウ繧ー

譛ャ譌・譛磯俣

注目のテーマ

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

RSSについて

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

メールマガジン登録

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