sed is fine until the delimiter fights you. sd takes a find string, a replace string, and a file. That is the whole interface.
sd is a find-and-replace tool. The first argument is the pattern, the second is the replacement, and any files after that are edited in place. No s///, no picking a delimiter because the path has slashes. On stdin it prints the result and does not wait to look clever. Checked on sd 1.1.0: in-place replace, -p preview (file unchanged), -F fixed strings, a capture-group replace, and a trailing-whitespace trim.
install
# macOS
brew install sd
# no package named sd for this tool in the default repos. Use the GitHub release. Do not apt-install a different sd
# not in the default repos under this name. Use the GitHub release
# Arch extra — this is the Rust find-and-replace, not a sound daemon
sudo pacman -S sd
examples
# Replace on stdin: foo bar foo becomes baz bar baz
echo 'foo bar foo' | sd foo baz
# Preview a file. -p prints the change and leaves the file alone
sd -p foo baz notes.txt
# Literal strings, not regex
sd -F 'foo.bar' 'foo_bar' notes.txt
# Capture groups
echo 'cargo +nightly watch' | sd '(\w+)\s+\+(\w+)\s+(\w+)' 'cmd: $1, channel: $2, subcmd: $3'
# Trim trailing whitespace. Default mode is line-by-line, so this does not eat the newline
echo 'lorem ipsum ' | sd '\s+$' ''
pro tip
A replacement that starts with a dash looks like a flag. Put -- before it: echo 'foo' | sd foo -- '-w'. To emit a literal dollar sign in the replacement, write $$. Across-line matches need -A; the default never sees the newline.
honest limits
sd edits files in place. There is no .bak unless you make one yourself. Preview first if the tree is not in git. It is not a structured rewrite — it will happily change a string inside a string you did not mean. Debian's package name collision is real: do not apt install sd and assume you got this tool.