Introduction
The regex_escape filter in Ansible escapes special regex characters in a string so it can be safely used as a literal match in regex operations. Without escaping, characters like ., *, +, (, ), [, ], {, }, ^, $, |, \, and ? are interpreted as regex metacharacters, causing unexpected matches or errors. This filter is essential when building dynamic regex patterns from variables that may contain special characters.
Syntax
# Basic syntax
"{{ my_variable | regex_escape() }}"
# With regex flavor (POSIX or Python — default is Python)
"{{ my_variable | regex_escape('posix_basic') }}"
"{{ my_variable | regex_escape('posix_extended') }}"
Parameters
Parameter | Required | Default | Description
───────────|──────────|──────────|──────────────────────────
re_type | No | "python" | Regex flavor: "python", "posix_basic", or "posix_extended"
Characters Escaped
Character | Meaning in Regex | After regex_escape
──────────|───────────────────────|───────────────────
. | Any character | \.
* | Zero or more | \*
+ | One or more | \+
? | Zero or one | \?
^ | Start of string | \^
$ | End of string | \$
| | Alternation (OR) | \|
( ) | Capture group | \( \)
[ ] | Character class | \[ \]
{ } | Quantifier | \{ \}
\ | Escape character | \\
Basic Examples
---
- name: Demonstrate regex_escape filter
hosts: localhost
gather_facts: false
vars:
filename: "config.backup[2026].tar.gz"
ip_address: "192.168.1.100"
version_string: "v2.0.0 (stable)"
path: "/opt/app/data+logs"
tasks:
# Without regex_escape — WRONG: . matches any character
- name: BAD — unescaped dots match anything
ansible.builtin.debug:
msg: "{{ 'config-backup_2026_.tar.gz' | regex_search(filename) }}"
# Matches! The dots and brackets match unintended characters
# With regex_escape — CORRECT: matches literal string only
- name: GOOD — escaped string matches literally
ansible.builtin.debug:
msg: "{{ 'config.backup[2026].tar.gz' | regex_search(filename | regex_escape()) }}"
# Only matches the exact string
- name: Escape IP address for regex search
ansible.builtin.set_fact:
ip_pattern: "{{ ip_address | regex_escape() }}"
# Result: "192\.168\.1\.100"
- name: Search for literal IP in config file
ansible.builtin.shell: grep '{{ ip_address | regex_escape() }}' /etc/hosts
register: grep_result
changed_when: false
failed_when: false
- name: Escape version string with parentheses
ansible.builtin.debug:
msg: "Escaped: {{ version_string | regex_escape() }}"
# Result: "v2\.0\.0\ \(stable\)"
- name: Escape path with plus sign
ansible.builtin.debug:
msg: "Escaped: {{ path | regex_escape() }}"
# Result: "/opt/app/data\+logs"
Practical Use Cases
Search for Literal Strings in Files
- name: Check if exact error message exists in log
vars:
error_msg: "ERROR: Connection failed (timeout=30s)"
ansible.builtin.lineinfile:
path: /var/log/app.log
regexp: "{{ error_msg | regex_escape() }}"
state: absent
check_mode: true
register: log_check
changed_when: false
- name: Report if error found
ansible.builtin.debug:
msg: "Error message found in log!"
when: log_check.found | default(0) > 0
Replace Literal Strings Containing Special Characters
- name: Replace old URL in config (dots and slashes need escaping)
vars:
old_url: "https://api.v1.example.com/data?format=json&limit=100"
new_url: "https://api.v2.example.com/data?format=json&limit=100"
ansible.builtin.replace:
path: /etc/app/config.yml
regexp: "{{ old_url | regex_escape() }}"
replace: "{{ new_url }}"
Dynamic Pattern Building
- name: Build regex pattern from variable list
vars:
blocked_ips:
- "10.0.0.1"
- "192.168.1.50"
- "172.16.0.100"
ansible.builtin.set_fact:
ip_pattern: "{{ blocked_ips | map('regex_escape') | join('|') }}"
# Result: "10\.0\.0\.1|192\.168\.1\.50|172\.16\.0\.100"
- name: Remove all blocked IPs from allowlist
ansible.builtin.lineinfile:
path: /etc/app/allowlist.conf
regexp: "^({{ ip_pattern }})$"
state: absent
Combining with regex_search and regex_replace
- name: Search for literal bracket notation in output
vars:
search_term: "status[0].ready"
ansible.builtin.set_fact:
found: "{{ api_output | regex_search(search_term | regex_escape()) }}"
- name: Replace version string preserving format
vars:
old_version: "app-server (2.1.0+build.456)"
new_version: "app-server (3.0.0+build.789)"
ansible.builtin.replace:
path: /etc/app/version
regexp: "{{ old_version | regex_escape() }}"
replace: "{{ new_version }}"
Common Mistakes
# ❌ MISTAKE 1: Forgetting to escape when variable has special chars
- name: BAD — dots in IP match any character
ansible.builtin.debug:
msg: "{{ log_line | regex_search(ip_address) }}"
# FIX:
- name: GOOD — escape the IP
ansible.builtin.debug:
msg: "{{ log_line | regex_search(ip_address | regex_escape()) }}"
# ❌ MISTAKE 2: Double-escaping (applying regex_escape to an already-escaped string)
# Only apply regex_escape once
# ❌ MISTAKE 3: Using regex_escape on the replacement string
# regex_escape is for the SEARCH pattern, not the replacement
- name: WRONG
ansible.builtin.replace:
path: /etc/config
regexp: "{{ old | regex_escape() }}"
replace: "{{ new | regex_escape() }}" # ← Don't escape replacement!
# FIX:
- name: RIGHT
ansible.builtin.replace:
path: /etc/config
regexp: "{{ old | regex_escape() }}"
replace: "{{ new }}" # ← Raw string for replacement
# ❌ MISTAKE 4: Escaping when you WANT regex behavior
# Don't use regex_escape if you intend the string to be a regex pattern
# Only use it when the string should match literally
regex_escape vs Manual Escaping
# Manual escaping is error-prone and hard to maintain:
regexp: "192\\.168\\.1\\.100"
# regex_escape is safer and works with any variable:
regexp: "{{ ip_address | regex_escape() }}"
# Especially important when the value comes from:
# - User input (set_fact, extra vars)
# - API responses
# - File contents
# - Dynamic inventory
Related Articles
- Ansible regex_search Filter
- Ansible regex_replace Filter
- Ansible Jinja2 Filters
- Ansible lineinfile Module
- Ansible map vs selectattr vs json_query
Conclusion
Use regex_escape() any time you build a regex pattern from a variable that might contain special characters. It prevents subtle bugs where dots match any character, brackets create character classes, or parentheses form capture groups. The filter is especially critical for IP addresses, file paths, URLs, version strings, and any user-provided input used in regex_search, regex_replace, lineinfile, or replace operations.