Help us understand the problem. What is going on with this article?

他言語からきてbashのif文でつまづいたので、if文に絞ったまとめ

はじめに

最近bash勉強し始めてif文で早速つまづいたので、ハマったところを記録します。
記事中のコードの動作確認をした環境は以下です。

  • bash 3.2.57 (macOS 10.13 High Sierra)

1. if文の角カッコ(ブラケット)前後には空白が必要

正しい例
#!/bin/bash
x="a"
if [[ "$x" = "a" ]]; then
  echo "hoge";
fi

if文の条件式前後の空白はあってもなくてもなよい言語も多いですが
以下のように空白なしで書くと

こう書いてはいけない
#!/bin/bash
x="a"
if [["$x" = "a"]]; then
#    ^        ^   ここに空白がないのでダメ
  echo "hoge";
fi 

↓こんな感じのエラーになります。

./a.sh: line 3: [[a: command not found

エラーにはならないけど、これは期待した動作にならない

エラーにはならないですが、以下のように書くと

こう書いてはいけない
#!/bin/bash
x="a"
if [[ "$x"="b" ]]; then
#       ^ ^    ここに空白がないとだめ
  echo "hoge"
fi

条件式としてではなく、文字列「"$x"="b"」が空文字かを評価します。
当然文字列としては空文字でないため、常に真として扱われてしまいます。

2. if ... fi 構文の改行はあってもなくてもOK

空白に対する厳しさと裏腹に、改行の有無に関しては割と自由です。
以下はすべて正常に動きます。ただし改行なしで書くときはセミコロンを忘れずに。

ifとthenを1行に
#!/bin/bash
x="a"
if [[ "$x" = "a" ]]; then # セミコロンを忘れずに!
  echo "A"
fi
thenのあとに続けてコマンド
#!/bin/bash
x="a"
if [[ "$x" = "a" ]]
then echo "A"
fi
すべて改行
#!/bin/bash
x="a"
if [[ "$x" = "a" ]]
then 
  echo "A"
fi
ワンライナー
#!/bin/bash
x="a"
if [[ "$x" = "a" ]];then  echo "A";fi # セミコロンを忘れずに!

3. [ ... ] と [[ ... ]] で使える条件式が(少し)違う

見た目だけの違い?と勘違いしてしまいますが、扱える条件式が違うので注意です。

[ ... ] による条件評価

コード例
#!/bin/bash
x="a"
if [ -n "$x" ];then  
  echo "A"
fi

こちらの条件式(文字列比較、数値比較、ファイル属性チェック)が使えます。
bashに依存させたくない場合で、POSIX準拠のshを書くときは[ ... ]を使います。

[[ ... ]] による条件評価

コード例
#!/bin/bash
x="a"
if [[ -n "$x" ]];then
  echo "A"
fi

[ ... ] と同じ条件式が使え、加えて [[ ... ]] 内では以下の条件式評価ができます。

  • && や || による複数条件の論理演算
  • == や != による文字列パターンマッチ
  • =~ による正規表現パターンマッチ

bashがある環境においては、
GoogleのShell Style GuideこちらのStackOverflowあたりだと[[ ... ]] が推奨されていました。
(逆にbashない環境でも動くシェルにしたければ[[ ... ]]は使わない)

[ ... ]と[ ... ] でAND、OR条件書くときの&&、||の位置

[ ... ] の場合

[ ... ]の外に&&や||を書きます。

正しい例
#!/bin/bash
if [ -e /etc/profile ] && [ -s /etc/profile ]; then
  echo "A"
fi

[ ... ]の内に&&や||を書くと構文エラーになります。

こう書いてはいけない
#!/bin/bash
if [ -e /etc/profile &&  -s /etc/profile ]; then
  echo "A"
fi

こんな感じのエラーです。

./a.sh: line 2: [: missing `]'

[[ ... ]] の場合

[[ ... ]]の内に&&や||が書けます。(外にも書ける)

正しい例(内側)
#!/bin/bash
if [[ -e /etc/profile  && -s /etc/profile ]]; then
  echo "hoge"
fi
正しい例(外側)
#!/bin/bash
if [[ -e /etc/profile ]] && [[ -s /etc/profile ]]; then
  echo "hoge"
fi

4. [ ... ] と [[ ... ]] でパス名展開(単語分割)の挙動が違う

パス名展開の挙動の違い

[ ... ]の場合

以下の*はパス名展開されてしまいます。なので、

パス名展開されてしまう
#!/bin/bash
if [ "filename" == f* ]; then
  echo "A"
fi

ファイル名がfで始まるファイルが複数あるディレクトリで↑を実行すると、
*がパス名展開されて、↓のエラーになります。

./a.sh: line 3: [: too many arguments

[[ ... ]]の場合

[[ ... ]] 内の*はパス名展開はされないので、実行すると"A"が表示されます。

文字列パターンマッチとして動作する
#!/bin/bash
if [[ "filename" == f* ]]; then
  echo "A"
fi

単語分割

[ ... ] の場合

変数に空白が含まれていると単語分割され、以下の例だと3つの値を渡すことになり、

こう書いてはいけない
x="a b c"
if [ -n $x ]; then
  echo "hoge"
fi

こんなエラーになります。

./a.sh: line 3: [: too many arguments

[[ ... ]] の場合

[[ ... ]]の中は、単語展開されないので、エラーになりません。

正しい例
x="a b c"
if [[ -n $x ]]; then
  echo "hoge"
fi

5. bash依存ならシバンでbashを指定する

1行目(シバン)でスクリプトが使用するインタプリタに/bin/bashを指定(厳密にはbashがインストールされてるパス)します。

bash依存ならこう書く
#!/bin/bash

以下のように/bin/shを指定し、かつbash依存[[が含まれるスクリプト。
一見問題なさそうですが、

こう書いてはいけない
#!/bin/sh
if [[ -e /tmp ]]; then echo "A"; fi

実行すると、Ubuntuでは以下のエラーになります。

./a.sh: 2: [[: not found

Ubuntu6.10以降は/bin/shのリンク先は規定ではdashであり、bashではありません。
dashには[[がないので、上記エラーとなります。

6. いろいろ書いたけど

とりあえず動くかすぐ確認したいときは、こんな便利なサイトがあります。

ShellCheck

すばらしい!

まとめ

bashのif文勉強し始めて、つまづいて学んだことは以下の5点でした。

  • if文の条件式前後は空白を入れる
  • if文は改行してもしなくてもよいが、セミコロンの場所に注意
  • 作成するシェルの動作環境に、bashがあるかを確認し
    • あるなら、if文の条件式に [[ ... ]] を使える、使う場合はシバンにbashのパスを明記
    • ないなら、if文の条件式に [ ... ] を使う
  • [ ... ]と[[ ... ]]では条件式やパス展開の挙動の違いがある
  • 正しく書けているかのチェックはShellCheck におまかせ

if文の学習で力つきそうで、繰り返し構文までたどりつけなそうです。

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした