Diary

Diary

日々学んだことをアウトプットする場として初めてみました

bash [[ 内での <,> は文字列比較になるよ

bash で変数の評価を行う際に [[ をよく使うんですが、その中の <, > の意味を間違っていたがために、無駄に時間を使ってしまう事件がありました。
(未来の自分を含めた)皆さんには変なところで躓いて欲しくないため、こちらにメモしておきます。

[目次]

時間ない人まとめ

- [[ は bash で拡張された色々な表現が使える
- <, > は文字列の辞書順の大小を表す
- 数値を比較したいとき
  - -gt, -lt 等を使う
  - [[..]] ではなく ((..)) を使う

[[ を使った評価について

シェルスクリプトには、変数を比較するためのコマンド [ があります。
これは test コマンドと同一であり、しばしば if と合わせて以下のように使用します。

# コマンド [ の戻り値が正常(0)かどうかを判断
if [ hoge = hoge ]; then echo same string; fi
same string

if [ 3 -eq 3 ]; then echo same integer; fi
same integer

# <, > は通常のリダイレクト等に解釈されるため使用不可
if [ 3 < 5 ]; then echo left is greater than right; fi
zsh: no such file or directory: 5

また、bash には [[ という拡張された表現があるのですが、正規表現のマッチもできるため私はよくこちらを利用しています。

if [[ -n "$1" ]] && [[ -f "$1" ]]; then
    FILE="$1"
fi

if [[ "$line" =~ ^(\#+)([ ]+)([^\#]*) ]]; then
    block_level="$(echo -n "${BASH_REMATCH[1]}" | wc -c | xargs)"
    ...
fi

<, > の扱いについて

こちらの表現で何気なく <, > を使ったところ、実行時エラーにならず処理が続いていきました。

if [[ 3 < 5 ]]; then echo left is greater than right; fi
left is greater than right

ぱっと見良さそうに見えますが、多くの人にとってこれは期待値とは異なる動きをします。

もうお分かりかと思いますが、実はこれは文字列での辞書順の比較になっています!

このことについて、マニュアルの bash: [[ についてのページにはきちんと記載があります。

Expressions are composed of the primaries described below in Bash Conditional Expressions.

こちらのコンディションを参考にすると、辞書式順序で比較する、と書いてあります。

string1 < string2
  True if string1 sorts before string2 lexicographically.
string1 > string2
  True if string1 sorts after string2 lexicographically.
if [[ 30 < 5 ]]; then echo left is greater than right; fi
left is greater than right

ではどうするか

[ の時同様 -lt, -gt 等を使う

先ほどの [[どう評価されるかの条件のページを見直すと、-lt, -le, -gt, -ge を使えばいいようです。

if [[ 30 -lt 5 ]]; then echo left is greater than right; fi

一応説明すると、以下のような略語になっている、と覚えるとはやいと思います。

  • gt: Greater Than
    • >
  • lt: Less Than
    • <
  • gt: Greater than or Equal
    • >=
  • le: Less than or Equal
    • \<=

数値の表現 *3 を使う

こちらのマニュアルを再度みてみると、(( が使えそうです。

(( expression ))

The arithmetic expression is evaluated according to the rules described below (see Shell Arithmetic). The expression undergoes the same expansions as if it were within double quotes,

この中では Shell Arithmetic という数式の表現が使えるとのことなので期待できそうです。
(注意としては、表現が 0 以外のとき返り値 0 (false), それ以外の時は返り値が 1 (true) になる、ということです)

# 良さそう
if (( 30 < 5 )); then echo pien; fi
if (( 3**3 > 5 )); then echo pien; fi
pien

if (( 3 )); then echo hi; fi
hi
if (( 0 )); then echo hi; fi

最近の言語と同じ感覚で使えそうですね。

リンク

おわりに

bash のドキュメントをみるとなんでも書いてあるので申し訳なくなる(もっと仲良くなりたい)。
bash 以外の移植性考えたら、なるべく [[ とか (( とか使わないほうがいいんだろうか。

*1:..

*2:..

*3:..