やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

bash 条件文の書き方(if, test, [], [[]])

 4種類もある。

成果物

記法一覧

方法 コード例 メリット デメリット
[[]] [[ 0 -eq 0 ]] 1. 正規表現が使える([[ "text" =~ ^.*$ ]])
2. スペースを含む変数値でもダブルクォート不要(var='a b'; [[ $var = 'a b' ]])
3. 最も可読性がマシ([[ 0 -eq 0 && 1 -eq 1 ]])
POSIX非互換
[] [ 0 -eq 0 ] && echo "TRUE" 短く書ける 左辺と右辺はスペースを含む場合ダブルクォートする必要あり
if if [ 0 -eq 0 ]; then echo "TRUE"; fi; 1. プログラミングっぽい
2. ググラビリティある
1. 冗長かつ可読性低
2. 条件式を変数にできない
test test 0 -eq 0; echo $? 1. 結果は$?で取得する必要あり面倒(すぐ取得せねば別コマンドの結果で上書きされてしまう)
2. 真: 0, 偽: 1とふつうと逆でわかりにくい

 上から順に推奨。[[]]が最も良さそう。

 testを使う意義がわからない。test[]とほぼ同じ。どちらもコマンドである。[]は最後の引数が]であることを強いられる点以外すべて同じ。

条件式を可変にする(eval

cond="0 -eq 0"
match=$(eval [[ "$cond" ]]; echo $?;)
echo $match
cond="0 -eq 0"
match=$(eval [ "$cond" ]; echo $?;)
echo $match
cond="0 -eq 0"
match=$(eval test "$cond"; echo $?;)
echo $match

 真なら0, 偽なら1C言語だと逆なのでわかりにくい。

コード例

#!/bin/bash
#set -Ceu
echo "----- [[  ]] -----"
[[ 0 -eq 0 ]]; echo $?
[[ 0 -eq 1 ]]; echo $?
[[ 0 -eq 0 ]] && echo "TRUE"
[[ 0 -eq 0 ]] && echo "TRUE1"; echo "TRUE2"
[[ 0 -eq 1 ]] && echo "TRUE" || echo "FALSE"
# 論理演算
[[ 0 -eq 0 && 1 -eq 1 ]] && echo "TRUE" || echo "FALSE"
[[ 0 -eq 0 || 1 -eq 2 ]] && echo "TRUE" || echo "FALSE"
# 計算
[[ 2**10 -eq 512+512 ]] && echo "Equal" || echo "Not equal"
[[ '2 ** 10' -eq '512 + 512' ]] && echo "Equal" || echo "Not equal"
# 文字列比較
[[ "a" = "a" ]] && echo "TRUE" || echo "FALSE"
[[ "a" = "b" ]] && echo "TRUE" || echo "FALSE"
[[ -z "" ]] && echo "TRUE" || echo "FALSE"
# ダブルクォート不要
var="a b"
[[ $var = "a b" ]] && echo "TRUE" || echo "FALSE"
# ワイルドカード
[[ '2000-01-01' = *-*-* ]] && echo "Match!!" || echo "Not match..."
[[ '2000-01-01' == *-*-* ]] && echo "Match!!" || echo "Not match..."
# 正規表現
[[ '2000-01-01' =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] && echo "Match!!" || echo "Not match..."
#   なぜかマッチしない \dのメタ文字がない? https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE
[[ '2000-01-01' =~ ^\d{4}-\d{2}-\d{2}$ ]] && echo "Match!!" || echo "Not match..."
[[ '2000-01-01' =~ ^[\d]{4}-[\d]{2}-[\d]{2}$ ]] && echo "Match!!" || echo "Not match..."
[[ '2000-01-01' =~ ^(\d){4}-(\d){2}-(\d){2}$ ]] && echo "Match!!" || echo "Not match..."

echo "----- [  ] -----"
[ 0 -eq 0 ]; echo $?
[ 0 -eq 1 ]; echo $?
[ 0 -eq 0 ] && echo "TRUE"
[ 0 -eq 0 ] && echo "TRUE1"; echo "TRUE2"
[ 0 -eq 1 ] && echo "TRUE" || echo "FALSE"
[ 0 -eq 0 -a 1 -eq 1 ] && echo "TRUE" || echo "FALSE"
[ 0 -eq 0 -o 1 -eq 2 ] && echo "TRUE" || echo "FALSE"
[ 0 -eq 0 ] && [ 1 -eq 1 ] && echo "TRUE" || echo "FALSE"
[ 0 -eq 0 ] || [ 1 -eq 2 ] && echo "TRUE" || echo "FALSE"
[ "a" = "a" ] && echo "TRUE" || echo "FALSE"
[ "a" = "b" ] && echo "TRUE" || echo "FALSE"
[ -z "" ] && echo "TRUE" || echo "FALSE"
var="a b"
[ "$var" = "a b" ] && echo "TRUE" || echo "FALSE"

echo "----- test -----"
test 0 -eq 0; echo $?
test 0 -eq 1; echo $?

echo "----- if -----"
if [ 0 -eq 0 ]; then echo "TRUE"; fi;
if [ 0 -eq 1 ]; then echo "TRUE"; else echo "FALSE"; fi;
if [ 0 -eq 1 ]; then echo "TRUE"; elif [ 0 -eq 0 ]; then echo "ELSE_IF"; else echo "FALSE"; fi;

# 複数行
if [ 0 -eq 0 ]; then
    echo "TRUE"
fi

if [ 0 -eq 1 ]; then
    echo "TRUE"
else
    echo "FALSE"
fi

if [ 0 -eq 1 ]; then
    echo "TRUE"
elif [ 0 -eq 0 ]; then
    echo "ELSE_IF"
else
    echo "FALSE"
fi

# 複数行(;なし)
if [ 0 -eq 0 ]
then
    echo "TRUE"
fi

if [ 0 -eq 1 ]
then
    echo "TRUE"
else
    echo "FALSE"
fi

if [ 0 -eq 1 ]
then
    echo "TRUE"
elif [ 0 -eq 0 ]
then
    echo "ELSE_IF"
else
    echo "FALSE"
fi

 できるだけ短く書きたい。するとifは書かなくなる。以下のような問題があるから。

  • 読みづらい&冗長&覚えづらい
    • then, fi, elif
    • 改行多すぎ
    • どこに;付与すればいいかわかりづらい

 どう考えても[[]]が最善。

条件式

条件式 説明
-a file file が存在すれば真。
-b file file が存在し、かつブロック特殊ファイルならば真。
-c file file が存在し、かつキャラクター特殊ファイルならば真。
-d file file が存在し、かつディレクトリならば真。
-e file file が存在すれば真。
-f file file が存在し、かつ通常ファイルならば真。
-g file file が存在し、かつ set-group-id されていれば真。
-h file file が存在し、かつシンボリックリンクならば真。
-k file file が存在し、かつ “sticky” ビットが設定されていれば真。
-p file file が存在し、かつ名前付きパイプ (FIFO) ならば真。
-r file file が存在し、かつ読み込み可能ならば真。
-s file file が存在し、かつそのサイズが 0 より大きければ真。
-t fd ファイル・ディスクリプター fd がオープンされており、かつ端末を参照していれば真。
-u file file が存在し、 かつ set-user-id ビットが設定されていれば真。
-w file file が存在し、かつ書き込み可能ならば真。
-x file file が存在し、かつ実行可能ならば真。
-G file file が存在し、かつ (実行中のシェルの) 実効グループ ID に所有されていれば真。
-L file file が存在し、かつシンボリックリンクならば真。
-N file file が存在し、 かつ前回読み込まれた以降に修正されていれば真。
-O file file が存在し、かつ (実行中のシェルの) 実効ユーザ ID に所有されていれば真。
-S file file が存在し、かつソケットならば真。
file1 -ef file2 file1 と file2 とで デバイス番号と i-ノード番号が同じならば真。
file1 -nt file2 file1 が (変更日時に関して) file2 より新しいか、 file1 が存在するが file2 が存在しなければ、真。
file1 -ot file2 file1 が file2 より古いか、 file2 が存在するのに file1 が存在しなければ、真。
-o optname シェルオプション optname が有効ならば真。後述する組み込みコマンド set に対するオプションの説明中にある -o オプションを参照してください。
-v varname シェル変数 varname がセットされている (値が代入されている) ならば真。
-z string string の長さが 0 ならば真。
-n string string の長さが 0 でなければ真。
string1 == string2
string1 = string2
文字列が同じならば真。( POSIX に準拠する形で test コマンドを使う場合には = を使う必要がある)
string1 != string2 2 つの文字列が異なれば真。
string1 < string2 現在のロケールにおいて、string1 が string2 よりも 辞書順で前にある場合に真。
string1 > string2 現在のロケールにおいて、string1 が string2 よりも 辞書順で後にある場合に真。
arg1 OP arg2 OP は -eq, -ne, -lt, -le, -gt, -ge. のいずれか。

 よく使うやつだけ。

FILE=a.txt
rm -Rf $FILE
[ -f $FILE ] && echo 'ファイルは存在する' || echo 'ファイルは存在しない'
touch $FILE
[ -f $FILE ] && echo 'ファイルは存在する' || echo 'ファイルは存在しない'
rm -Rf $FILE

DIR=dir
rm -Rf $DIR
[ -d $DIR ] && echo 'ディレクトリは存在する' || echo 'ディレクトリは存在しない'
mkdir $DIR
[ -d $DIR ] && echo 'ディレクトリは存在する' || echo 'ディレクトリは存在しない'
rm -Rf $DIR

[ -v FILE ] && echo '変数名は存在する' || echo '変数名は存在しない'
[ -v FFFF ] && echo '変数名は存在する' || echo '変数名は存在しない'
[ -z $FILE ] && echo '変数名は長さゼロ-z' || echo '変数名は長さゼロでない-z'
ZERO=
[ -z $ZERO ] && echo '変数名は長さゼロ-z' || echo '変数名は長さゼロでない-z'

[ -n $FILE ] && echo '変数名は長さゼロでない-n' || echo '変数名は長さゼロ-n'
[ -n $ZERO ] && echo '変数名は長さゼロでない-n' || echo '変数名は長さゼロ-n'

[ "$FILE" = 'a.txt' ] && echo '文字列は等しい' || echo '文字列は等しくない'
[ "$FILE" = 'b.txt' ] && echo '文字列は等しい' || echo '文字列は等しくない'
[ "$FILE" != 'a.txt' ] && echo '文字列は等しい' || echo '文字列は等しくない'
[ "$FILE" != 'b.txt' ] && echo '文字列は等しい' || echo '文字列は等しくない'

[ 1 -eq 1 ] && echo '-eq'
[ 1 -ne 2 ] && echo '-ne '
[ 1 -lt 2 ] && echo '-lt'
[ 1 -le 1 ] && echo '-le'
[ 2 -gt 1 ] && echo '-gt'
[ 1 -ge 1 ] && echo '-ge'

参考

対象環境

  • Raspbierry pi 3 Model B+
  • Raspbian stretch 9.0 2018-11-13
  • bash 4.4.12
    • zenity 3.28.1
    • xsel 1.2.0
    • xdotool 3.20160805.1
$ uname -a
Linux raspberrypi 4.14.98-v7+ #1200 SMP Tue Feb 12 20:27:48 GMT 2019 armv7l GNU/Linux