sd Advanced Patterns
Advanced find-and-replace patterns using sd (simpler than sed).
Regex Patterns
Capture Groups
# Reorder parts
sd '(\w+)@(\w+)\.com' '$2/$1' emails.txt
# john@example.com → example/john
# Wrap in function
sd 'console\.log\((.*)\)' 'logger.info($1)' src/**/*.js
# console.log(msg) → logger.info(msg)
# Extract and transform
sd 'class (\w+)' 'export class $1' src/**/*.ts
Word Boundaries
# Match whole words only
sd '\bfoo\b' 'bar' file.txt
# "foo" matches, "foobar" doesn't
# Function rename without affecting variables
sd '\bgetUser\b' 'fetchUser' src/**/*.ts
Optional and Alternation
# Optional whitespace
sd 'function\s*\(' 'const fn = (' src/**/*.js
# Match alternatives
sd '(let|var)\s+' 'const ' src/**/*.js
Multiline Patterns
# Match across lines (-s flag)
sd -s 'start\n.*\nend' 'replacement' file.txt
# Remove multiline blocks
sd -s '\/\*\*[\s\S]*?\*\/' '' src/**/*.ts
Real-World Patterns
Import Transformations
# CommonJS to ES modules
sd "const (\w+) = require\('([^']+)'\)" "import $1 from '$2'" src/**/*.js
# Relative to absolute imports
sd "from '\.\./\.\./utils'" "from '@/utils'" src/**/*.ts
# Named imports reorganization
sd "import \{ (\w+), (\w+) \}" "import { $2, $1 }" src/**/*.ts
API Endpoint Updates
# Version bump
sd '/api/v1/' '/api/v2/' src/**/*.ts
# Domain migration
sd 'api\.old\.com' 'api.new.com' src/**/*.ts
# Path restructuring
sd '/users/(\d+)/posts' '/posts?user_id=$1' src/**/*.ts
Configuration Updates
# Environment variable rename
sd 'DATABASE_URL' 'DB_CONNECTION_STRING' .env* src/**/*.ts
# Port number change
sd 'port:\s*3000' 'port: 8080' **/*.yaml **/*.json
# Version strings
sd '"version":\s*"\d+\.\d+\.\d+"' '"version": "2.0.0"' package.json
Code Modernization
# Async/await from promises
sd '\.then\(\((\w+)\)\s*=>\s*\{' 'const $1 = await ' src/**/*.ts
# Template literals
sd '"\s*\+\s*(\w+)\s*\+\s*"' '${$1}' src/**/*.ts
# Optional chaining
sd '(\w+)\s*&&\s*\1\.(\w+)' '$1?.$2' src/**/*.ts
React/JSX Patterns
# className to class (or vice versa)
sd 'className="' 'class="' src/**/*.jsx
sd 'class="' 'className="' src/**/*.jsx
# Event handler rename
sd 'onClick=\{' 'onPress={' src/**/*.tsx
# Hook migration
sd 'componentDidMount\(\)' 'useEffect(() => {' src/**/*.tsx
Remove Code
# Console logs
sd 'console\.log\([^)]*\);?\s*\n?' '' src/**/*.ts
# Debug statements
sd '// DEBUG:.*\n' '' src/**/*.ts
# Commented code blocks
sd '//\s*[A-Za-z].*\n' '' src/**/*.ts # Single line comments with code
# TODO comments
sd '// TODO:.*\n' '' src/**/*.ts
Batch Workflows
Safe Preview Pattern
# 1. List affected files
rg -l 'oldPattern' src/
# 2. Preview replacements (rg with -r)
rg 'oldPattern' -r 'newPattern' src/
# 3. Apply to found files
sd 'oldPattern' 'newPattern' $(rg -l 'oldPattern' src/)
# 4. Verify
rg 'oldPattern' src/ # Should return nothing
# 5. Review changes
git diff
With fd for File Selection
# TypeScript files only
fd -e ts -x sd 'old' 'new' {}
# Specific directories
fd -e js . src/ lib/ -x sd 'old' 'new' {}
# Exclude patterns
fd -e ts -E "*.test.ts" -E "*.spec.ts" -x sd 'old' 'new' {}
# By filename pattern
fd 'config' -e json -x sd '"dev"' '"prod"' {}
Multiple Replacements
# Chain with &&
sd 'pattern1' 'replacement1' file.txt && \
sd 'pattern2' 'replacement2' file.txt && \
sd 'pattern3' 'replacement3' file.txt
# Or use a script
#!/bin/bash
files=$(rg -l 'oldApi' src/)
for file in $files; do
sd 'oldApi\.get' 'newApi.fetch' "$file"
sd 'oldApi\.post' 'newApi.send' "$file"
sd 'oldApi\.delete' 'newApi.remove' "$file"
done
Special Characters
Escaping Reference
| Character |
Escape |
Example |
. |
\. |
sd '1\.0' '2.0' |
* |
\* |
sd '\*important\*' '**important**' |
? |
\? |
sd 'what\?' 'what!' |
[ ] |
\[ \] |
sd '\[x\]' '[✓]' |
( ) |
\( \) |
sd 'func\(\)' 'func(arg)' |
{ } |
\{ \} |
sd '\{0,1\}' '?' |
$ |
\$ |
sd '\$100' '€100' |
^ |
\^ |
sd '\^note' 'NOTE' |
\ |
\\ |
sd '\\n' '\n' |
\| |
\| |
sd 'a\|b' 'a or b' |
Literal Mode
# When you have many special chars, consider preprocessing
# or use fixed-string replacement with rg first
# For checking matches
rg -F '[TODO]' src/
# sd doesn't have -F, so escape carefully
sd '\[TODO\]' '[DONE]' src/**/*.md
Platform-Specific
With Git
# Only changed files
sd 'old' 'new' $(git diff --name-only)
# Staged files only
sd 'old' 'new' $(git diff --cached --name-only)
# Files changed in last commit
sd 'old' 'new' $(git diff-tree --no-commit-id --name-only -r HEAD)
In Docker
# Inside container
docker exec -it container sd 'old' 'new' /app/config.json
# Or with volume mount
docker run -v $(pwd):/data alpine sh -c "apk add sd && sd 'old' 'new' /data/file.txt"
Tips and Best Practices
| Tip |
Reason |
Always preview with rg -r first |
Catch mistakes before applying |
| Use git before bulk changes |
Easy rollback with git checkout |
| Quote patterns |
Prevent shell interpretation |
Use \b for word boundaries |
Avoid partial matches |
| Start specific, then broaden |
Easier to control scope |
| Test on single file first |
Verify pattern works |
Use fd -x for file selection |
More precise than globs |