できない.dev

bash の if で「unary operator expected」「too many arguments」になる

[ ] の中の変数を引用符で囲んでいないと、空文字や空白を含む値が単語分割で消えたり増えたりして構文が壊れる。
変数を "$var" で囲むか、bash なら [[ ]] を使えば解消する。

公開: 更新:

実行例あり(2026-09-12 に実環境で検証)

要約

条件分岐を書いただけなのに、実行するとエラーが出る。

name=""
if [ $name = "alice" ]; then echo hit; fi
bash: [: =: unary operator expected

原因は [ がシェルの構文ではなくコマンドで、引数の個数がそのまま意味を決めることである。$name が空文字だと単語分割で消えてしまい、[ は = と alice の 2 引数だけを受け取る。
2 引数の場合は「単項演算子+値」と解釈されるため、= が単項演算子として扱われて破綻する。

値に空白が入ると逆に引数が増える。

name="alice smith"
if [ $name = "alice" ]; then echo hit; fi
bash: [: too many arguments

どちらも変数を引用符で囲むだけで直る。

実行例

クオートしていない変数が空のまま [ $name = alice ] に展開されると、bash -x の + '[' = alice ']' が示すとおり左辺ごと消えて [: =: unary operator expected になる。
値に空白が入る場合は語が増えて [: too many arguments に変わり、変数を "$name" とクオートした版ではどちらの入力でもエラーなく分岐している。

$ bash bad.sh
bad.sh: line 2: [: =: unary operator expected
$ bash bad2.sh
bad2.sh: line 2: [: too many arguments
$ bash good.sh
empty: no match, no error
spaced: no match, no error
$ bash -x bad.sh
+ name=
+ '[' = alice ']'
bad.sh: line 2: [: =: unary operator expected

— 2026-09-12 時点の出力

検証環境

検証日
実行環境
ubuntu:24.04Ubuntu 24.04.4 LTS
バージョン
  • Bash 5.2.21

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

よくある原因

  1. 変数が空で引数が減る: 未設定の変数や空文字は、Word Splitting(新しいタブで開く) の結果として引数そのものが消える。[ $x = y ] は [ = y ] になり、unary operator expected が出る。
  2. 値に空白があって引数が増える: alice smith のような値は 2 語に割れる。[ alice smith = alice ] は 4 引数となり、too many arguments になる。
  3. 値がワイルドカードを含む: x="*" のときパス名展開が働き、カレントディレクトリのファイル名すべてに置き換わる。
    ファイルが 1 つなら偶然動き、2 つ以上あると突然壊れるので原因を掴みにくい。
  4. [ を構文だと思っている: [ は test の別名にあたるコマンドで、閉じ括弧の ] すら最後の引数でしかない。
    したがって空白の入れ方や引用符の有無が、そのまま引数の切れ目になる。
  5. 単項演算子に渡す値が空: [ -n $x ] も同じ罠を踏む。$x が消えると [ -n ] になり、これは「文字列 -n が空でないか」を判定する 1 引数の形になるため、常に真を返す。
    エラーにならず黙って誤判定するぶん、こちらのほうが厄介である。

解決策

1. 変数を引用符で囲む

もっとも基本的で、sh でも通る対処である。

name=""
if [ "$name" = "alice" ]; then echo hit; fi

引用符の中では単語分割もパス名展開も起こらないため、$name が空でも空白入りでも常に 1 引数として渡る。[ の中に出てくる変数参照は、例外なく囲むと決めてしまうのがよい。

2. bash なら [[ ]] を使う

bash や zsh 専用のスクリプトなら [[ ]] を使う。
これはコマンドではなくシェルの構文なので、Conditional Constructs(新しいタブで開く) が説明しているとおり、内部で単語分割とパス名展開が行われない。

name="alice smith"
if [[ $name = "alice" ]]; then echo hit; fi

=~ による正規表現マッチや && || の直接記述も使える。
ただし #!/bin/sh を宣言したスクリプトでは使えないので、移植性が要るなら方法 1 を選ぶ。

3. 既定値を与えて空文字を避ける

set -u を有効にしていると未設定変数の参照自体がエラーになる。
既定値展開を併用すると両方まとめて防げる。

set -u
if [ "${name:-}" = "alice" ]; then echo hit; fi

${name:-default} の形にすれば、未設定時の値も指定できる。

4. bash -x で展開結果を見る

エラーの原因は「展開後に引数が何個になったか」なので、それを直接見るのが早い。

bash -x script.sh
+ name=
+ '[' = alice ']'

トレース行に '[' = alice ']' と出ていれば、変数が消えて 2 引数になっていることが確定する。
修正後は + '[' '' = alice ']' のように空文字が 1 引数として残る形になる。

この記事は役立ちましたか?