Escaping & Special Characters
Every context has its own escaping rules. Master these to avoid hours of debugging quoting issues.
|
How to Grow This Reference
When to add:
Where to add:
Format:
Maintenance:
|
Quick Reference
| Context | Escape Char | Key Rules |
|---|---|---|
Shell (single quotes) |
None needed |
|
Shell (double quotes) |
|
|
Regex (BRE) |
|
|
Regex (ERE/PCRE) |
|
|
AsciiDoc stem:[] |
|
|
sed |
|
Use |
awk |
|
|
xargs |
|
Use |
The Four Quoting Styles
| Style | Behavior | Example |
|---|---|---|
|
Literal - nothing expands |
|
|
Variables expand, some escapes work |
|
|
ANSI-C quoting - |
|
Unquoted |
Word splitting, glob expansion |
|
When to Use Each
# Single quotes: regex patterns, literal strings
grep 'pattern with $special [chars]' file
# Double quotes: variables, command substitution
echo "User: $USER, PWD: $(pwd)"
# ANSI-C: escape sequences needed
echo $'Column1\tColumn2\nData1\tData2'
# Here-string with expansion
cat <<< "Current dir: $PWD"
# Here-string literal (note quotes around EOF)
cat <<'EOF'
$HOME stays literal here
EOF
Common Gotchas
# WRONG: single quotes prevent variable expansion
echo 'Hello $USER' # prints: Hello $USER
# RIGHT: double quotes allow expansion
echo "Hello $USER" # prints: Hello evanusmodestus
# WRONG: unquoted glob expands
echo * # prints all files
# RIGHT: quoted glob is literal
echo "*" # prints: *
# Escaping inside double quotes
echo "Cost: \$100" # prints: Cost: $100
echo "She said \"hello\"" # prints: She said "hello"
BRE vs ERE vs PCRE
| Metachar | BRE | ERE | PCRE |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Escaping Literal Metacharacters
# Match literal dot (not "any char")
grep '\.' file # BRE
grep -E '\.' file # ERE
grep -P '\.' file # PCRE
# Match literal brackets
grep '\[error\]' file # matches [error]
# Match literal asterisk
grep '\*' file # matches *
# Match literal backslash
grep '\\' file # BRE: two backslashes
grep -E '\\\\' file # ERE in double quotes: four!
grep -P '\\' file # PCRE: two backslashes
Character Classes Don’t Need Escaping (mostly)
# Inside [...], most chars are literal
grep '[.*+?]' file # matches literal . * + ?
# Exceptions inside [...]
grep '[]]' file # ] must be first: matches ]
grep '[^]]' file # ^ negates only at start
grep '[-a-z]' file # - literal at start/end
grep '[a-z-]' file # - literal at end
Brackets in stem:[] Macros
The \$...\$ macro uses [] for its delimiter. LaTeX nth roots \sqrt[n]{x} conflict.
// WRONG - AsciiDoc parses [n] as attribute
stem:[\sqrt[n]{a}] // Error: Could not find closing ']'
// RIGHT - escape both brackets
stem:[\sqrt\[n\]{a}] // Renders correctly
Curly Braces (Attributes)
// WRONG - interpreted as attribute
The value is {undefined} // Renders blank or warning
// RIGHT - escape opening brace
The value is \{undefined} // Renders: {undefined}
// Or use passthrough
The value is pass:[{undefined}]
Passthrough for Complex Cases
// Inline passthrough
pass:[<special>content</special>]
// Block passthrough
++++
<div>Raw HTML here</div>
++++
Delimiter Flexibility
# Default delimiter - must escape slashes
sed 's/\/usr\/local/\/opt/' file
# Better - use different delimiter
sed 's|/usr/local|/opt|' file
sed 's#/usr/local#/opt#' file
sed 's@/usr/local@/opt@' file
Special Replacement Characters
# & = entire matched string
sed 's/[0-9]\+/[&]/' file # 123 becomes [123]
# Escape for literal &
sed 's/AT&T/AT\&T Corp/' file
# Backreferences
sed 's/\(foo\)\(bar\)/\2\1/' file # foobar -> barfoo
Newlines and Tabs
# Insert newline (GNU sed)
sed 's/:/:\n/g' file
# Insert tab
sed 's/:/:\t/g' file
# Or use $'...' quoting
sed $'s/:/:\t/g' file
String vs Regex Context
# Regex context - no quotes, slashes delimit
awk '/pattern/ { print }' file
# String context - double quotes
awk '{ print "Hello, World" }' file
# Regex from variable - use ~ operator
awk -v pat="error" '$0 ~ pat { print }' file
Escaping Inside Strings
# Double quote inside string
awk '{ print "She said \"hello\"" }' file
# Backslash needs doubling
awk '{ print "path: C:\\Users" }' file
# Newline and tab
awk '{ print "line1\nline2" }' file
awk '{ print "col1\tcol2" }' file
Field Separator Escaping
# Pipe as separator - escape in regex
awk -F'\\|' '{ print $1 }' file
# Or use bracket
awk -F'[|]' '{ print $1 }' file
# Dot as separator
awk -F'\\.' '{ print $1 }' file
Filename Safety
# WRONG: breaks on spaces and special chars
find . -name "*.log" | xargs rm
# RIGHT: null-delimited (safest)
find . -name "*.log" -print0 | xargs -0 rm
# RIGHT: quote placeholder
find . -name "*.log" | xargs -I {} rm "{}"
Quoting with -I Placeholder
# Placeholder gets substituted - needs quoting for spaces
find . -type f | xargs -I {} echo "Processing: {}"
# Multiple uses of placeholder
find . -name "*.bak" | xargs -I {} mv {} {}.old
# Custom placeholder (useful when {} conflicts)
find . -name "*.txt" | xargs -I FILE cp FILE /backup/
Escaping in Executed Commands
# Pass to shell for complex commands
find . -name "*.log" | xargs -I {} sh -c 'echo "File: $1"; wc -l "$1"' _ {}
# The _ is a placeholder for $0, {} becomes $1
# Quotes around $1 handle spaces in filenames
Parallel with Special Chars
# Parallel processing with null delimiter
find . -type f -print0 | xargs -0 -P 4 -n 1 gzip
# -P 4 = 4 parallel processes
# -n 1 = one file per process
# -0 = null delimiter (handles ANY filename)
Real Patterns (Production Use)
Patterns extracted from actual work sessions - proof they work.
Patterns From Production (2026-03)
awk: Split PEM Certificate Chain
# Extract first cert (leaf) from chain
awk '/-----BEGIN CERTIFICATE-----/{n++} n==1' chain.pem > leaf.pem
# Extract second cert (issuing CA)
awk '/-----BEGIN CERTIFICATE-----/{n++} n==2' chain.pem > issuing-ca.pem
# Extract third cert (root CA)
awk '/-----BEGIN CERTIFICATE-----/{n++} n==3' chain.pem > root-ca.pem
Used: 2026-03-05 ISE certificate deployment
sed: Pipe Delimiter for Paths
# WRONG: escaping hell with slashes
sed -i 's/\/usr\/bin\/qemu-system-x86_64/\/usr\/libexec\/qemu-kvm/' file.xml
# RIGHT: use pipe delimiter - no escaping needed
sed -i 's|/usr/bin/qemu-system-x86_64|/usr/libexec/qemu-kvm|' file.xml
Used: 2026-03-07 WLC VM migration
awk: Extract IP from Command Output
# Get IP from ip addr output
ip -4 addr show eth0 | awk '/inet/{print $2}'
# Get just IP without CIDR
ip -4 addr show eth0 | awk '/inet/{split($2,a,"/"); print a[1]}'
# Get default route interface
ip route | awk '/default/{print $5}'
Used: 2026-03-05 network diagnostics
grep -E: Multiple Patterns
# Match any of multiple VLAN interfaces
show interfaces | grep -E 'eth0\.(20|30|40)'
# Match bridge member status
bridge link show | grep -E "eno8.*master"
# Match active connections
nmcli conn show --active | awk 'NR>1 {print $1, $3}'
Used: 2026-03-05 VyOS troubleshooting
awk: Line Range Extraction
# Show first 5 and last 5 lines with line numbers
awk 'NR<=5 {print NR": "$0; next} {buf[NR]=$0} END {print "..."; for(i=NR-4;i<=NR;i++) print i": "buf[i]}' file
# Extract specific line range from runbook
awk 'NR==689 || NR==700' docs/pages/runbooks/ise-deployment.adoc
# Show section of output (from pattern to blank line)
/usr/local/bin/kvm-health-check 2>&1 | awk '/Virtualization/,/^$/'
Used: 2026-03-01, 2026-03-05 runbook navigation
find + xargs: Safe Bulk Operations
# Find and process with confirmation
find . -name "*.bak" -print0 | xargs -0 -p rm
# Find and grep with parallel
find . -name "*.adoc" -print0 | xargs -0 -P 4 grep -l "TODO"
# Find and sed in-place (GNU)
find . -name "*.conf" -print0 | xargs -0 sed -i 's/old/new/g'
Used: throughout March 2026
Lessons Learned
This section captures real gotchas encountered in production.
2026-03-22: AsciiDoc stem nth root
Problem: MathJax error "Could not find closing ']'" in formulas.adoc
Root Cause: \$\sqrt[n\${a}] - AsciiDoc parsed [n] as attribute bracket
Fix: Escape both brackets: \$\sqrt\[n]\{a}\$
Rule: In inline \$...\$, always escape \sqrt\[n\] brackets