sd: Find and Replace Without sed Pain
sd FIND REPLACE rewrites text, taking a regex and replacement as two plain arguments and editing files in place with no sed-style delimiters or flags.
sd removes the sed footguns: no s/// syntax, no -i "" portability dance between GNU and BSD, and $1 capture groups that just work.
What it does
sd takes a find pattern (a Rust regex by default) and a replacement string. Given file arguments it edits them in place; with no files it reads stdin and writes stdout. Capture groups are referenced as $1, $2, or ${name}. -s/--string-mode treats the pattern as a literal.
Common usage
echo 'hello world' | sd world there # stdin -> stdout
sd 'v(\d+)\.(\d+)' 'version $1.$2' README.md # capture groups, in place
sd -s '1.2.3' '1.3.0' package.json # literal string mode
sd '\r\n' '\n' file.txt # CRLF -> LF
fd -e ts -x sd 'old_api' 'new_api' # across many filesOptions
| Flag | What it does |
|---|---|
| -s / --string-mode | Treat FIND and REPLACE as literal strings |
| -p / --preview | Show a diff of changes without writing |
| -f / --flags <flags> | Regex flags: i (case), m (multiline), s (dotall) |
| $1, ${name} | Reference numbered or named capture groups |
| -n / --max-replacements <n> | Replace at most n occurrences |
In CI
sd edits in place by default, so a misfire mutates committed files; run --preview first or rely on a clean git worktree to catch unexpected changes. Because the same command works identically on Linux and macOS, sd avoids the sed -i portability bug that breaks the same script across runner OSes.
Common errors in CI
"sd: command not found" (not preinstalled): install with cargo install sd, brew install sd, or apt-get install -y sd on newer distros. A replacement that silently does nothing usually means the regex did not match; remember . and ( are regex metacharacters unless you pass -s. Using \1 instead of $1 for a capture group inserts a literal backslash-one.