# sd Advanced Patterns Advanced find-and-replace patterns using sd (simpler than sed). ## Regex Patterns ### Capture Groups ```bash # 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 ```bash # 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 ```bash # Optional whitespace sd 'function\s*\(' 'const fn = (' src/**/*.js # Match alternatives sd '(let|var)\s+' 'const ' src/**/*.js ``` ### Multiline Patterns ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 |