Skip to content

Why Does Bash Require Escaping Leading Curly Braces in Regex?

1. The Problem

I was writing a Bash script to match strings containing curly braces using regex. The pattern looked straightforward:

script.sh
regex="{foo"
string="{foo"
[[ $string =~ $regex ]] && echo "matched" || echo "no match"

On my local machine running Bash 3.x, this worked perfectly. But when I deployed the same script to a production server running Bash 4.x, it suddenly failed:

no match

The string "{foo" should match the pattern "{foo", right? Why wasn’t it working?

2. Investigation

I started debugging by checking my Bash versions:

Check Bash versions
# Local machine
$ bash --version
GNU bash, version 3.2.57(1)-release
# Production server
$ bash --version
GNU bash, version 4.4.20(1)-release

The only difference was the Bash version. This was clearly a version-specific behavior change.

I then tested various escaping strategies:

escape_tests.sh
# Test 1: No escape - fails in Bash 4.x
regex="{foo"
[[ "{foo" =~ $regex ]] && echo "test1: matched" || echo "test1: no match"
# Result: test1: no match (Bash 4.x)
# Test 2: Escaped brace - works in both versions
regex="\{foo"
[[ "{foo" =~ $regex ]] && echo "test2: matched" || echo "test2: no match"
# Result: test2: matched
# Test 3: Quoted within pattern - works in both versions
[[ "{foo" =~ '{'foo ]] && echo "test3: matched" || echo "test3: no match"
# Result: test3: matched

The pattern worked when I escaped the curly brace with a backslash. But why?

3. Why Does Bash Require This Escape?

The answer lies in how Bash interprets regex patterns containing curly braces.

According to POSIX regex(7) documentation, a { followed by a non-digit character should be treated as a literal brace, not as a quantifier. However, Bash 4.x introduced stricter parsing that requires explicit escaping to avoid ambiguity.

This was a behavioral change from Bash 3.x, which was more lenient with this rule. Scripts that worked in Bash 3.x could break when run in Bash 4.x.

4. Solutions

4.1 Solution 1: Escape with Backslash

The most straightforward fix is to escape the curly brace with a backslash:

solution1.sh
regex="\{foo"
string="{foo"
[[ $string =~ $regex ]] && echo "matched" || echo "no match"
# Result: matched

This works in both Bash 3.x and 4.x, ensuring backward compatibility.

4.2 Solution 2: Quote the Brace Within the Pattern

You can also quote the brace directly within the regex pattern:

solution2.sh
string="{foo"
[[ "$string" =~ '{'foo ]] && echo "matched" || echo "no match"
# Result: matched

This approach treats the quoted portion as a literal string, bypassing the quantifier interpretation.

4.3 Solution 3: Use a Variable for the Brace

For more complex patterns, you can store the brace in a variable:

solution3.sh
brace="{"
pattern="${brace}foo"
string="{foo"
[[ $string =~ $pattern ]] && echo "matched" || echo "no match"
# Result: matched

5. Practical Example: JSON Pattern Matching

Here’s a real-world scenario where this issue commonly appears - matching JSON-like structures:

json_match.sh
# Trying to match JSON containing curly braces
json_string='{"name":"test","value":123}'
# This FAILS in Bash 4.x
regex='{"name":"test"'
[[ $json_string =~ $regex ]] && echo "matched" || echo "no match"
# Result: no match
# This WORKS in both Bash 3.x and 4.x
regex='\{"name":"test"'
[[ $json_string =~ $regex ]] && echo "matched" || echo "no match"
# Result: matched

6. Summary

When writing Bash regex patterns that start with a curly brace {, always escape it as \{ to ensure compatibility across Bash versions.

Bash VersionUnescaped {Escaped \{
Bash 3.xWorksWorks
Bash 4.xFailsWorks

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments