How to compare strings in Bash

Compare strings in Bash with [[ "$a" == "$b" ]], test for inequality, and sort strings lexically with < and >. Copy-paste examples with quoting rules.

Tldr

Use `[[ "$a" == "$b" ]]` for equality and `[[ "$a" != "$b" ]]` for inequality. Always quote both sides. Use `<` and `>` inside `[[ ]]` for lexical ordering (unquoted operators, since Bash needs to distinguish them from redirection). Avoid the single-bracket `[ "$a" = "$b" ]` unless you need POSIX portability.

Intro

String comparison is one of the most common sources of quoting bugs in Bash. This guide covers the modern [[ ]] syntax, the portable [ ] syntax, pattern matching, and lexical ordering.

Steps

Name

Compare for equality with [[ ]]

Text

The double-bracket [[ ]] is a Bash keyword, not a command, so it handles unquoted variables more safely — but you should still quote for clarity and to avoid pattern-matching surprises on the right-hand side.

Name

Compare for inequality

Text

Use != inside [[ ]] the same way as ==.

Name

Use = for POSIX-portable scripts

Text

The single-bracket [ ] test command is POSIX and works in dash/sh too, but requires strict quoting — an unquoted empty variable can turn into a syntax error.

Name

Do lexical ordering comparisons

Text

Inside [[ ]], < and > compare strings in the current locale's collation order. They must be unquoted operators (not escaped) or Bash will treat them as redirection inside [ ].

Name

Match against a pattern, not just literal equality

Text

The right-hand side of == inside [[ ]] is treated as a glob pattern when unquoted, which is powerful but easy to misuse. Quote it if you want a literal string match.

Faq

Q

What is the difference between == and = in Bash?

A

Inside [[ ]], == and = are equivalent for string equality. Inside the POSIX [ ] test, only = is portable; == works in Bash's [ ] as an extension but is not POSIX-compliant.

Q

Why did my string comparison silently do a pattern match?

A

Inside [[ ]], an unquoted right-hand operand after == or != is treated as a glob pattern. `[[ "$x" == *.log ]]` matches any string ending in .log, not the literal string "*.log". Quote the right side if you want a literal comparison.

Q

How do I compare strings case-insensitively?

A

Lowercase both sides first with parameter expansion: `[[ "${a,,}" == "${b,,}" ]]` (Bash 4+), or set `shopt -s nocasematch` to make == in [[ ]] case-insensitive for the current shell.