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:

  • You hit a quoting/escaping bug that took >5 min to debug

  • You discover a pattern that works across tools

  • You find a gotcha that would bite future-you

Where to add:

  • Edit examples/codex/text/escaping.adoc (source of truth)

  • New pattern in existing section? Add to that tag block

  • New tool? Create new tag: // tag::toolname-escaping[]

  • Real bug? Add to lessons-learned section with date

Format:

  • Show WRONG then RIGHT (learn from mistakes)

  • Include the actual command you used

  • Add comment explaining WHY it works

Maintenance:

  • Review quarterly - delete patterns you’ve internalized

  • Star (*) patterns you keep forgetting

Quick Reference

Context Escape Char Key Rules

Shell (single quotes)

None needed

'literal $VAR' - nothing expands

Shell (double quotes)

\

"$VAR expands" - use \$ for literal

Regex (BRE)

\

\+, \?, \|, \(\) need escaping

Regex (ERE/PCRE)

\

+? (pipe) () work, escape for literal

AsciiDoc stem:[]

\

\[n\] for brackets inside stem

sed

\ or delimiter

Use \| as delimiter to avoid \/

awk

\

\" inside strings, /regex/ unescaped

xargs

-0 or "{}"

Use -print0 (pipe) xargs -0 for safety

The Four Quoting Styles

Style Behavior Example

'…​'

Literal - nothing expands

'$HOME stays literal'

"…​"

Variables expand, some escapes work

"Home is $HOME"

$'…​'

ANSI-C quoting - \n, \t, \x work

$'line1\nline2'

Unquoted

Word splitting, glob expansion

*.txt expands to files

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

+ (one or more)

\+

+

+

? (zero or one)

\?

?

?

| (alternation)

|

|

|

() (grouping)

\(\)

()

()

{} (quantifier)

\{\}

{}

{}

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