Reading — step 1 of 7
Learn
Bash 3+ has built-in regex support via the =~ operator. Combined with the BASH_REMATCH array for capture groups, it replaces many uses of grep and sed. Bash Reference Manual covers it; ABS Guide goes deeper.
Basic =~ matching
str="hello world"
if [[ "$str" =~ ^hello ]]; then
echo "starts with hello"
fi
if [[ "$str" =~ wor ]]; then
echo "contains 'wor'"
fi
=~ returns true if the right-hand pattern matches anywhere in the left-hand string. The pattern is an extended regex (ERE) — like grep -E.
Capture groups — BASH_REMATCH
email="[email protected]"
if [[ "$email" =~ ^([^@]+)@([^@]+)$ ]]; then
user="${BASH_REMATCH[1]}"
domain="${BASH_REMATCH[2]}"
echo "user=$user domain=$domain"
fi
BASH_REMATCH[0]— entire matchBASH_REMATCH[1],[2], ... — capture groups
Caveat: BASH_REMATCH is set by the LAST regex match. Save immediately if you'll do another match.
Common patterns
# IP address validation:
[[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
# UUID:
[[ "$uuid" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]
# Numeric:
[[ "$x" =~ ^-?[0-9]+(\.[0-9]+)?$ ]]
# Semver-ish:
[[ "$v" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]
# Extract date parts:
if [[ "$date" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then
year="${BASH_REMATCH[1]}"
month="${BASH_REMATCH[2]}"
day="${BASH_REMATCH[3]}"
fi
Quoting matters — DON'T quote the regex
# WRONG — quotes treat regex as literal string
[[ "hello" =~ "hel.*" ]] # matches literal "hel.*", not pattern
# RIGHT — unquoted regex
[[ "hello" =~ hel.* ]]
# OR — store regex in a variable:
pat='^hel.*'
[[ "hello" =~ $pat ]] # variable expansion is OK
Bash 3.2+ defaults to literal matching when the regex is quoted. Counterintuitive — always store regexes in unquoted variables for clarity.
Globbing vs regex
Different worlds:
- Glob (
*.txt,?,[abc]) — for filename matching withcase,[[ "$x" == pattern ]] - Regex (
.*,[0-9]+) — for=~
Don't confuse them. [[ "$x" == "*.txt" ]] is a literal string match unless you don't quote the pattern.
case with patterns — alternative to regex
case "$file" in
*.tar.gz|*.tgz) tar xzf "$file" ;;
*.zip) unzip "$file" ;;
*.tar) tar xf "$file" ;;
*) echo "unknown" ;;
esac
Glob patterns inside case. Often simpler than regex when matching filename suffixes.
When NOT to use bash regex
For complex parsing (JSON, XML, source code), use a real tool:
jqfor JSONxmllint,xmlstarletfor XMLawk,grep -P(PCRE) for harder text patterns
Bash regex shines for simple validation and extraction. Don't try to write a parser.
Common mistakes
- Quoting the regex — Bash 3.2+ treats quoted regex as literal. Use unquoted or store in a variable.
- Forgetting
[[ ... ]]—=~only works inside[[ ]], not[ ]or(( )). - Locale-sensitive ranges —
[A-Z]may match accented characters in some locales. Use[[:upper:]]or set LC_ALL=C. - BASH_REMATCH overwritten — only the most-recent match's captures are available. Save immediately.
- Treating
=~like a global match — it returns the FIRST match only. For all matches, loop or use external tools.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…