Bash Regex: =~ and BASH_REMATCH

Tested on RHEL 10.2 (Coughlan)
Package bash 5.2.26-6.el10
Applies to Linux distributions with Bash installed
Privilege Normal user
Scope [[ string =~ regex ]], POSIX extended regular expressions (ERE), capture groups with BASH_REMATCH, regex variables, RHS quoting, backslash escaping, case-insensitive matching, and exit status 2 on invalid patterns. Does not cover PCRE, grep -P, or full input-validation algorithms for IPv4 or semantic versions.
Related guides Bash shell script tutorial
Shell scripting course hub
Short on time?Beta Get the gist of this 9-minute read in seconds, then ask follow-up questions

Bash can match POSIX extended regular expressions directly inside [[ … ]] with the =~ operator. Parenthesized groups are stored in BASH_REMATCH, which lets a script extract matched text without calling grep or sed.

Matching is substring-based by default; use ^ and $ when the whole value must match. Bash =~ follows POSIX ERE rules, not PCRE. For simple contains, prefix, or suffix checks without regex metacharacters, see compare strings in Bash — glob patterns on == are often clearer.


Bash Regex in One Example

Pull a date out of a log line and capture the YYYY-MM-DD portion:

bash
line='started 2026-09-09 10:15'

if [[ $line =~ ([0-9]{4}-[0-9]{2}-[0-9]{2}) ]]; then
  echo "date=${BASH_REMATCH[1]}"
fi
output
date=2026-09-09

BASH_REMATCH[0] holds the full matched substring; BASH_REMATCH[1] holds the first parenthesized group. Save captures to your own variables before another =~ test replaces or clears the array.


[[ string =~ regex ]] Syntax

=~ works only inside Bash [[ … ]]. It is not available in POSIX [ … ] — see Bash vs sh when a script must stay POSIX-portable.

text
[[ string =~ regex ]]

The left operand is the string to search. The right operand is an extended regular expression. When the pattern matches anywhere in the string, the conditional succeeds with exit status 0; otherwise it fails with status 1.

Combine it with Bash if, elif, else like any other [[ ]] test:

bash
email='user@example.com'

if [[ $email =~ @ ]]; then
  echo 'contains @'
fi
output
contains @

For bracket-level differences between [ ], [[ ]], and (( )), see Bash [ ] vs [[ ]] vs (( )).


Bash Uses POSIX ERE, Not PCRE

Bash =~ follows POSIX extended regular expression rules. That is not the same as PCRE (Perl-compatible regex) used by tools such as grep -P on some systems.

Feature Bash =~ (ERE) PCRE (grep -P, many languages)
Digit class [0-9] or [[:digit:]] Often \d
Word boundary Not portable as \b \b
Lookahead No (?=…)
Non-capturing groups (?:…) not in ERE Supported
Lazy/non-greedy quantifiers No *?, +? Supported

A pattern that works in Python or JavaScript may need rewriting for Bash. POSIX ERE uses leftmost-longest matching rules; Bash does not provide PCRE-style lazy quantifiers such as .*?. Prefer POSIX character classes such as [[:digit:]] and [[:alpha:]] when you want locale-aware classes instead of raw ASCII ranges.


Substring Match vs ^...$ Full Match

By default, =~ succeeds when the pattern matches any substring. It does not require the whole string to match.

bash
s=abc123

if [[ $s =~ [0-9]+ ]]; then
  echo 'substring match'
fi
output
substring match

A line of digits anchored to the full string fails when other characters are present:

bash
s=abc123

if [[ $s =~ ^[0-9]+$ ]]; then
  echo 'full digits'
else
  echo 'not full digits'
fi
output
not full digits

Use ^ at the start and $ at the end when you need whole-string validation — for example checking that a variable contains only digits. For focused examples that compare digit-only checks with substring, prefix, suffix, and alphanumeric tests, see how to check what a Bash string contains.


Capture Groups with BASH_REMATCH

Parentheses in the regex create capture groups. After a successful =~ match, Bash fills the BASH_REMATCH array:

Index Content
[0] Full matched substring
[1] First capture group
[2] Second capture group
… Additional groups

Parse a key=value token from a log line:

bash
line='level=error user=admin'

if [[ $line =~ ^([^=]+)=([^[:space:]]+) ]]; then
  echo "key=${BASH_REMATCH[1]} val=${BASH_REMATCH[2]}"
fi
output
key=level val=error

BASH_REMATCH is a special global Bash array. Do not declare local BASH_REMATCH inside a function; Bash documents that doing so can produce unexpected results.

Copy captures you need before evaluating another =~ expression. A later regex test can replace or clear BASH_REMATCH:

bash
[[ abc123 =~ ([a-z]+)([0-9]+) ]]
echo "letters=${BASH_REMATCH[1]}"

[[ nope =~ ([0-9]+) ]]

declare -p BASH_REMATCH
output
letters=abc
declare -a BASH_REMATCH=()

A later =~ evaluation changes BASH_REMATCH: a successful match replaces it with new captures, while a failed match can clear it. Save BASH_REMATCH entries to your own variables when you still need them after more regex tests.

A shape regex can split major.minor.patch into parts. It does not prove the numbers are semantically valid — 999.999.999 still matches the pattern:

bash
ver=2.10.3

if [[ $ver =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
  echo "major=${BASH_REMATCH[1]} minor=${BASH_REMATCH[2]} patch=${BASH_REMATCH[3]}"
fi
output
major=2 minor=10 patch=3

For strict semantic-version ordering or range checks, compare parsed integers or use a dedicated tool — a regex alone cannot validate version semantics.


How to Get All Regex Matches in Bash

=~ records one match per evaluation; it is not a global-match operator. To find repeated occurrences in one string, evaluate the regex repeatedly inside a Bash while loop while removing the text through each match:

bash
text='id=12 id=34 id=56'
re='[0-9]+'

while [[ $text =~ $re ]]; do
  match=${BASH_REMATCH[0]}
  printf '%s\n' "$match"
  text=${text#*"$match"}
done
output
12
34
56

For large files or line-oriented extraction, grep -oE or another text-processing tool is usually more convenient than looping in the shell.


Store Regex in a Variable

Putting the pattern in a Bash variables assignment keeps complex expressions readable and makes backslash escaping easier.

bash
date_re='^[0-9]{4}-[0-9]{2}-[0-9]{2}$'
d=2026-09-09

if [[ $d =~ $date_re ]]; then
  echo 'date format ok'
fi
output
date format ok

When the regex is unquoted on the right of =~, Bash treats it as ERE syntax. When it comes from a variable expansion, metacharacters in the variable value are active — which is what you want for a stored pattern.


The RHS Quoting Trap

Quoting any part of the right-hand regex makes that quoted portion match literally. An unquoted pattern treats ^ and $ as anchors; a quoted pattern compares against literal characters.

bash
s=hello

if [[ $s =~ ^hello$ ]]; then
  echo 'unquoted ok'
fi
output
unquoted ok

The same test with a quoted regex fails because Bash looks for the literal text ^hello$:

bash
s=hello

if [[ $s =~ "^hello$" ]]; then
  echo 'quoted ok'
else
  echo 'quoted failed'
fi
output
quoted failed

Quote only the parts you intend to be literal — usually by building the pattern in a variable, not by wrapping the entire ERE in double quotes on the RHS.


Escaping Backslashes Correctly

Shell quoting and regex backslash interpretation happen in separate stages — count backslashes for each layer. Storing the pattern in a variable is the most reliable approach.

Match a filename ending in .txt:

bash
suffix_re='\.txt$'
file=report.txt

if [[ $file =~ $suffix_re ]]; then
  echo 'dot-txt match'
fi
output
dot-txt match

Single quotes preserve the backslash, so suffix_re='\.txt$' stores one backslash before the dot. The regex engine interprets \. as a literal period.

Patterns copied from regex101 or PCRE tools often use \d for digits. That token is not POSIX ERE syntax:

bash
re='\d+'
s=abc123

if [[ $s =~ $re ]]; then
  echo 'pcre match'
else
  echo 'no PCRE \d'
fi
output
no PCRE \d

Use [0-9] or [[:digit:]] instead of \d.


Case-Insensitive Matching

By default =~ is case-sensitive. Enable nocasematch for the current shell when you need case-insensitive ERE matching:

bash
shopt -s nocasematch

if [[ HELLO =~ ^hello$ ]]; then
  echo 'nocase ok'
fi

shopt -u nocasematch
output
nocase ok

nocasematch also affects ==, !=, and =~ inside [[ ]]. Turn it off when the block ends so later tests do not inherit case folding. For exact string equality without shell options, ${var,,} may be simpler than enabling case folding for the whole comparison.


Regex Exit Status and Invalid Patterns

A successful =~ match returns exit status 0. No match returns 1. A syntactically invalid ERE returns 2 — see Bash exit codes for how if interprets these values.

bash
[[ x =~ [invalid ]]
echo "exit=$?"
output
exit=2

Status 2 means the pattern itself is invalid, not that the string failed to match. If a regex is constructed dynamically, distinguish status 2 from status 1: 1 means no match, while 2 means the regex itself is invalid.


Regex vs Glob vs grep

Tool Syntax Best for
[[ s == *pat* ]] Shell glob on == Simple contains, prefix, suffix
[[ s =~ ere ]] POSIX ERE Captures, alternation, counted repeats in shell logic
grep -E ERE on file/stream Filtering lines from files or pipelines

Use Bash globbing on == when * and ? are enough. Use =~ when the value is already in a shell variable and the result controls shell logic. Use grep -E when you are filtering lines from files or pipelines.


Common Regex Mistakes

Mistake What goes wrong Fix
Quoted entire RHS ^ and $ become literal Leave ERE unquoted or use a variable
Expecting PCRE \d, \b Not valid in Bash ERE Use [0-9], [[:digit:]], or explicit boundaries
No ^ / $ on validation Substring match passes partial input Anchor the full string when validating
Reusing BASH_REMATCH later A later =~ evaluation can replace or clear the array Copy to your own variables immediately
Shape regex for IPv4 or semver Accepts out-of-range values Treat regex as format check only; validate ranges separately
Invalid ERE syntax Exit status 2, not a clean false Test patterns in development

Summary

Bash regex matching lives in [[ string =~ regex ]], uses POSIX ERE rather than PCRE, and fills BASH_REMATCH when parenthesized groups match. Substring matching is the default — add ^ and $ when the entire string must conform to the pattern.

Common problems are quoting the regex operand, using PCRE-only syntax such as \d, and reading BASH_REMATCH after another regex test has changed it. Store complex patterns in variables, save captures you still need, and treat exit status 2 as a syntax error in the pattern itself.

For simple text contains checks without regex syntax, prefer glob patterns on ==. Reach for =~ when you need capture groups, counted repetitions, or alternation that globs cannot express.


References


Frequently Asked Questions

Why does a quoted regex not match?

Quoting the right-hand side of =~ makes Bash treat the pattern as literal text. Anchors such as ^ and $, quantifiers, and groups are not active inside quotes. Store the ERE in a variable and use [[ string =~ $var ]] instead of quoting the pattern on the RHS.

Is Bash regex PCRE?

No. Bash =~ uses POSIX extended regular expressions. Features such as \d, (?:…), and lookahead are not available. Use [0-9] or [[:digit:]] for digits, and grep -P only when you explicitly need PCRE on a supported system.

How do I access capture groups?

After a successful =~ match, read BASH_REMATCH[1], BASH_REMATCH[2], and so on. Index 0 is the full match. Copy values to your own variables before another =~ test replaces or clears the array.

Does =~ match the full string?

Not by default. =~ succeeds when the pattern matches any substring. Use ^ at the beginning and $ at the end of the pattern when the entire string must match.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)