Bash error: "[: too many arguments"

Bash "[: too many arguments" means an unquoted variable with spaces word-split inside [ ]. Quote it, or use [[ ]], which never word-splits.

Error String

bash: [: too many arguments

Tldr

The `[` command (alias `test`) expects a fixed argument layout. An unquoted variable that holds whitespace word-splits into multiple args and breaks parsing. Quote the variable, or switch to `[[ ]]` which doesn't word-split.

Cause

In `[ "$x" = foo ]`, when `$x` is unquoted and equals "a b", `[` sees `[ a b = foo ]` — four arguments before `]` instead of three. The parser gives up with "too many arguments".

Repro

#!/usr/bin/env bash
x="hello world"
if [ $x = "hello" ]; then        # too many arguments
  echo match
fi

Fix

#!/usr/bin/env bash
x="hello world"
if [ "$x" = "hello" ]; then
  echo match
fi

# Or use [[ ]] which handles whitespace correctly:
if [[ $x == "hello" ]]; then
  echo match
fi

Explanation

`[[ ]]` is a Bash keyword (not a command) and does not split unquoted variables. It also supports == with glob patterns and =~ for regex. Prefer it for any Bash-only script.

Related Shellcheck

SC2086

Deep Dive

Heading

How `[` counts its arguments

Body

`[` is an ordinary command whose last argument must be `]`. A binary comparison is exactly four words: `[`, left operand, operator, right operand, `]`. When an unquoted expansion contains whitespace, the shell splits it into several words before `[` ever runs, so the command receives five or more and reports "too many arguments". The same happens when the expansion contains a glob that matches multiple filenames, or when command substitution returns a multi-word line.

Heading

Empty values cause the mirror-image error

Body

If the unquoted variable is empty rather than multi-word, it disappears entirely and `[ = "hello" ]` becomes a three-word test — Bash then reports "unary operator expected" instead. Both messages have the same root cause: an unquoted expansion. Quoting fixes both at once, which is why SC2086 is the single highest-value lint rule in shell scripts.

Heading

When to prefer `[[ ]]`, `[ ]`, or `case`

Body

In a Bash-only script use `[[ ]]`: it is a shell keyword, does not word-split unquoted expansions, and supports `==` glob matching and `=~` regex. In a `#!/bin/sh` script `[[ ]]` is not available, so keep `[ ]` and quote every operand. For matching a value against several alternatives, `case "$x" in ...)` is clearer and safer than chained tests, and it works in both shells.

Checklist

Find the `[` on the reported line and count the words after expansion — an unquoted variable holding spaces is the usual culprit.

Quote every operand: `[ "$x" = "hello" ]`.

In a Bash script, switch the test to `[[ $x == "hello" ]]`.

Check for command substitution operands — `[ $(cmd) = x ]` splits whenever cmd prints more than one word.

Re-run a lint pass; SC2086 findings elsewhere in the script are the same bug waiting to happen.

Faq

Q

Does quoting break numeric comparisons?

A

No. `[ "$n" -gt 0 ]` works fine — quoting prevents word-splitting, it does not change how -gt parses the value.

Q

Why does `[[ ]]` not need quotes?

A

`[[ ]]` is parsed by the shell as a keyword, so unquoted expansions inside it are not word-split or glob-expanded. Quote the right-hand side anyway when you want a literal match rather than a pattern.

Q

Is `test` different from `[`?

A

No — `test` and `[` are the same command; `[` simply requires a closing `]`. Both split unquoted arguments identically.