skip to content

With a=5 and b=10 in a bash script, what do `[ "$a" -gt "$b" ]`, `[ "$a" > "$b" ]` and `[[ $a > $b ]]` each do, and which of them silently creates a file?

level: middleimportance: should knowfreq 58%

answer

  1. -gt is for integers only
  2. a bare > is a redirection
  3. look for a stray file afterwards
  4. the keyword compares text, not numbers
  5. escape it, or use -gt

basics

~20 s

-gt compares integers, so it is false. Inside [ ], > is not an operator: the shell reads it as output redirection, creates a file named 10, and tests the single argument 5, which is true. Inside [[ ]], > compares strings, so 5 beats 10.

solid answer

~40 s

`[ "$a" -gt "$b" ]` is the numeric comparison — 5 is not greater than 10, so it is false. `[ "$a" > "$b" ]` is the trap: to the parser `>` is output redirection, not a test operator, so bash runs `[ 5 ]` — a single non-empty argument, therefore **true** — and creates or truncates a file literally named `10` in the current directory. `[[ $a > $b ]]` compares the operands as **strings**, and "5" sorts after "1", so that is true too. Three spellings of "greater than", three different answers. Use `-gt`/`-lt`/`-ge`/`-le` for integers, `[[ ]]` with `>`/`<` when you really want lexical order (or `\>` if you must stay in `[ ]`), and `(( a > b ))` when you are already doing arithmetic.

code

bash · 5 lines
bash
cd "$(mktemp -d)" || exit 1
a=5 b=10
[ "$a" -gt "$b" ]; echo "numeric -gt -> $?"
[ "$a" > "$b" ];   echo "with > -> $? ; directory now holds: $(ls)"
[[ $a > $b ]];     echo "string > -> $?"

go deeper

for a junior

Learn the pairing: -gt, -lt and -eq for numbers; = and != for text. If you catch yourself typing a bare > inside [ ], stop — that is a redirection.

for a middle

Explain why the redirection happens: [ is a command, and the shell removes redirections before building its argument list, so the test degenerates to a single non-empty operand. Then contrast it with [[ ]], where > is lexical comparison.

for a senior

Show how this reaches production: the condition is always true, nothing is logged, and a stray file appears in the working directory. Argue for a linter in CI as the systematic defence, since review alone will not catch a one-character mistake.

for a principal

Own the standard for comparisons in shell code — including what happens to values that arrive as text from an API or a file, and whether ordering must be locale-stable across build machines. Decide when the comparison has outgrown shell and belongs in a real program.

## Three operators that all look like "greater than" Bash gives you numeric comparison, string comparison and redirection in shapes that are one character apart, and only one of the three does what a newcomer reading `>` expects. ### `[ "$a" -gt "$b" ]` — the numeric one `-gt`, `-ge`, `-lt`, `-le`, `-eq`, `-ne` are the integer comparison operators of the `test` builtin. With a=5 and b=10 the answer is false (status 1), which is the arithmetically correct result. These are the operators to use whenever the values are numbers. ### `[ "$a" > "$b" ]` — not a comparison at all Remember that `[` is an ordinary command. The shell parses the line for redirections **before** deciding what arguments the command gets, and `>` is the redirection operator. So bash strips `> "$b"` out of the argument list, opens a file named `10` for writing (creating it, or truncating an existing one), and runs `[ 5 ]`. A single-argument `test` is the "is this string non-empty?" form. `5` is non-empty, so the command succeeds. Your condition returns **true regardless of the numbers**, and it litters the working directory with files named after whatever the right-hand side happened to be: ```bash a=5 b=10 [ "$a" > "$b" ]; echo $? # 0 — and ./10 now exists ``` This is the class of bug that makes shell scripting feel treacherous: no error, no warning, a plausible-looking line, a wrong branch, and a mysterious file that a later `rm` or directory listing turns into a second incident. ShellCheck flags it, which is one of the better arguments for running a linter over scripts. If you genuinely want `test`'s string comparison you must hide the character from the parser: `[ "$a" \> "$b" ]` (or `[ "$a" '>' "$b" ]`) compares the two operands lexically. It works, but nobody reads it comfortably. ### `[[ $a > $b ]]` — string comparison Inside the `[[ ]]` keyword the shell parses the construct itself, so `>` is not a redirection; it is the lexical comparison operator. It compares the operands as **text**: "5" against "10", character by character, so `5 > 10` is true because `5` sorts after `1`. Nothing about it is numeric. That is the right answer for ordering names, tags or identifiers, and precisely the wrong one for numbers — which is exactly the mistake this question is designed to catch, because the syntax looks the most like ordinary maths. ## Numeric operands that are not numbers The two brackets also disagree on what happens when a "number" turns out to be text. `[ "$x" -eq 0 ]` with `x=abc` fails loudly: bash prints `[: abc: integer expression expected` and exits with status 2. It insists on an integer literal — even `[ "1 + 1" -eq 2 ]` is an error. `[[ $x -eq 0 ]]` does not fail. Inside `[[ ]]`, the operands of the numeric operators go through **arithmetic evaluation**, where a bare word is treated as a variable name and an unset or empty variable is zero. So `[[ abc -eq 0 ]]` is *true*, and `[[ $x -eq 0 ]]` is true when `$x` is empty. A guard written that way accepts garbage as "zero". If a value came from outside the script, validate its shape before comparing it — for example with a regex match — rather than trusting a numeric operator to reject it. ## Related traps in the same family - **`=` versus `-eq`.** `[ "$a" = "$b" ]` is string equality: `5` and `05` are different strings. `[ "$a" -eq "$b" ]` is numeric equality: they are equal. Choose according to what the value *means*, not what it looks like. - **Locale.** In `[[ ]]`, `<` and `>` sort using the current locale's collation from bash 4.1 onward; older bash compared byte values. If ordering must be stable across machines and locales, do not rely on the default — set `LC_ALL=C` for that comparison or compare explicitly. - **Version strings.** Neither operator orders `1.10.0` against `1.9.0` correctly: numerically they are not integers, lexically `1.10` sorts before `1.9`. Split the fields and compare them one at a time, or hand the job to a tool that understands version order. ## The habit to build Ask what the values *are*. Integers get `-gt` and friends, or `(( ))` if you are already doing arithmetic. Text gets `=`, `!=`, or `[[ ]]`'s `<`/`>` for ordering. And treat a bare `>` inside `[ ]` as a defect on sight — it is never what the author meant.

  • Why does `[[ $x -eq 0 ]]` not complain when `$x` is a word like `abc`?
    Because inside `[[ ]]` the operands of the numeric operators go through arithmetic evaluation, where a bare word is read as a variable name and an unset or empty variable evaluates to 0. So `[[ abc -eq 0 ]]` is true. The `[` builtin instead demands an integer literal and errors with "integer expression expected". Validate untrusted numbers explicitly rather than relying on either behaviour.
  • How would you compare two version strings such as 1.10.0 and 1.9.0?
    Not with these operators. Numeric comparison rejects them as non-integers, and lexical comparison puts `1.10` before `1.9`. Either split on the dots and compare field by field as integers, or delegate the ordering to a tool that implements version sort and check which value comes first. Whichever you pick, add a test with a two-digit component — that is the case that exposes the naive implementation.
  • What is the difference between `[ "$a" != "$b" ]` and `[ "$a" -ne "$b" ]` when a=5 and b=05?
    `!=` is string inequality, so `5` and `05` are different and it is true. `-ne` is numeric inequality, so they are equal and it is false. The values look interchangeable but the operators answer different questions — which is why zero-padded ids, quantities read from a file, and numbers arriving as text need a deliberate choice rather than whichever operator you typed first.

saying these in an interview costs you the question

  • Uses > inside [ ] to compare two numbers
  • Says -gt also works for ordering text
  • Thinks [[ 5 > 10 ]] is false because 5 is smaller
  • Uses = for numbers and -eq for strings
  • Never notices the stray file the redirection left behind

context