2018-12-24 00:24:43 +00:00
# sd - s[earch] & d[isplace]
2018-12-26 00:01:11 +00:00
`sd` is an intuitive find & replace CLI.
2018-12-24 00:24:43 +00:00
2018-12-26 00:01:11 +00:00
## The Pitch
Why use it over any existing tools?
2018-12-24 00:24:43 +00:00
**Painless regular expressions**
2018-12-26 00:01:11 +00:00
`sd` uses regex syntax that you already know from JavaScript and Python. Forget about dealing with quirks of `sed` or `awk` - get productive immediately.
2018-12-24 00:24:43 +00:00
**String-literal mode**
2018-12-26 07:32:26 +00:00
Non-regex find & replace. No more backslashes or remembering which characters are special and need to be escaped.
2018-12-24 00:24:43 +00:00
**Easy to read, easy to write**
2018-12-26 21:36:08 +00:00
Find & replace expressions are split up, which makes them easy to read and write. No more messing with unclosed and escaped slashes.
2018-12-24 00:24:43 +00:00
2018-12-30 05:04:38 +00:00
**Smart, common-sense defaults**
2019-06-18 18:07:02 +00:00
Defaults follow common sense and are tailored for typical daily use.
2018-12-30 05:04:38 +00:00
2018-12-24 00:24:43 +00:00
## Comparison to sed
2018-12-26 00:01:11 +00:00
While sed does a whole lot more, `sd` focuses on doing just one thing and doing it well.
2018-12-24 00:46:02 +00:00
2018-12-24 00:24:43 +00:00
Some cherry-picked examples, where `sd` shines:
2018-12-27 00:42:58 +00:00
- Simpler syntax for replacing all occurrences:
- sd: `sd before after`
- sed: `sed s/before/after/g`
2018-12-24 00:24:43 +00:00
- Replace newlines with commas:
2018-12-31 22:05:51 +00:00
- sd: `sd '\n' ','`
- sed: `sed ':a;N;$!ba;s/\n/,/g'`
2018-12-27 00:42:58 +00:00
- Extracting stuff out of strings containing slashes:
- sd: `echo "sample with /path/" | sd '.*(/.*/)' '$1'`
2018-12-30 05:04:38 +00:00
- sed: use different delimiters every time depending on expression so that the command is not completely unreadable
- `echo "sample with /path/" | sed -E 's/.*(/.*/)/\1/g'`
2018-12-27 00:42:58 +00:00
- `echo "sample with /path/" | sed -E 's|.*(/.*/)|\1|g'`
- In place modification of files:
2019-06-18 18:07:02 +00:00
- sd: `sd before after file.txt`
2018-12-30 05:04:38 +00:00
- sed: you need to remember to use `-e` or else some platforms will consider the next argument to be a backup suffix
2018-12-27 00:42:58 +00:00
- `sed -i -e 's/before/after/g' file.txt`
2018-12-31 08:38:51 +00:00
## Benchmarks
**Simple replacement on ~1.5 gigabytes of JSON**
`hyperfine -w 3 'sed -E "s/\"/\'/g" *.json >/dev/null' 'sd "\"" "\'" *.json >/dev/null' --export-markdown out.md`
| Command | Mean [s] | Min…Max [s] |
|:---|---:|---:|
| `sed -E "s/\"/'/g" *.json >/dev/null` | 2.338 ± 0.008 | 2.332…2.358 |
| `sed "s/\"/'/g" *.json >/dev/null` | 2.365 ± 0.009 | 2.351…2.378 |
| `sd "\"" "'" *.json >/dev/null` | **0.997 ± 0.006** | 0.987…1.007 |
Result: ~2.35 times faster
**Regex replacement on a ~55M json file**:
```
hyperfine \
'sed -E "s:(\w+):\1\1:g" dump.json >/dev/null'\
"sed 's:\(\w\+\):\1\1:g' dump.json >/dev/null"\
'sd "(\w+)" "$1$1" dump.json >/dev/null'
```
| Command | Mean [s] | Min…Max [s] |
|:---|---:|---:|
| `sed -E "s:(\w+):\1\1:g" dump.json >/dev/null` | 11.315 ± 0.215 | 11.102…11.725 |
| `sed 's:\(\w\+\):\1\1:g' dump.json >/dev/null` | 11.239 ± 0.208 | 11.057…11.762 |
| `sd "(\w+)" "$1$1" dump.json >/dev/null` | **0.942 ± 0.004** | 0.936…0.951 |
Result: ~11.93 times faster
2018-12-24 00:24:43 +00:00
2018-12-26 21:54:59 +00:00
## Installation
2018-12-28 00:39:53 +00:00
### Cargo
2019-07-09 01:28:06 +00:00
[Cargo ](https://doc.rust-lang.org/cargo/getting-started/installation.html ) is the Rust package manager.
2019-07-09 01:29:39 +00:00
You can install cargo by
2019-07-09 01:28:06 +00:00
```sh
curl https://sh.rustup.rs -sSf | sh
```
2019-07-09 01:29:39 +00:00
Then
2018-12-26 21:54:59 +00:00
```sh
cargo install sd
```
2019-06-19 21:56:29 +00:00
### Alpine Linux
```sh
apk add sd
```
Before installing, ensure the appropriate [repository ](https://pkgs.alpinelinux.org/packages?name=sd ) is enabled.
2018-12-30 05:04:38 +00:00
### Arch Linux
2018-12-28 00:39:53 +00:00
2019-05-13 18:05:24 +00:00
```sh
pacman -S sd
```
2019-02-27 02:45:10 +00:00
2019-07-20 18:56:52 +00:00
### Fedora
```sh
dnf install sd
```
2019-02-27 02:45:10 +00:00
### FreeBSD
```sh
pkg install sd
```
2018-12-26 07:32:26 +00:00
## Quick Guide
2018-12-24 03:57:36 +00:00
2018-12-26 19:23:13 +00:00
1. **String-literal mode** . By default, expressions are treated as regex. Use `-s` or `--string-mode` to disable regex.
2018-12-24 03:57:36 +00:00
```sh
2018-12-26 19:23:13 +00:00
> echo 'lots((([]))) of special chars' | sd -s '((([])))' ''
2018-12-24 03:57:36 +00:00
lots of special chars
```
2018-12-24 04:01:20 +00:00
2. **Basic regex use** - let's trim some trailing whitespace
2018-12-24 03:57:36 +00:00
```sh
2018-12-26 19:23:13 +00:00
> echo 'lorem ipsum 23 ' | sd '\s+$' ''
2018-12-24 03:57:36 +00:00
lorem ipsum 23
```
2018-12-24 04:01:20 +00:00
3. **Capture groups**
2018-12-24 03:57:36 +00:00
Indexed capture groups:
```sh
2018-12-26 19:23:13 +00:00
> echo 'cargo +nightly watch' | sd '(\w+)\s+\+(\w+)\s+(\w+)' 'cmd: $1, channel: $2, subcmd: $3'
2018-12-24 03:57:36 +00:00
cmd: cargo, channel: nightly, subcmd: watch
```
Named capture groups:
```sh
2018-12-26 19:23:13 +00:00
> echo "123.45" | sd '(?P<dollars>\d+)\.(?P<cents>\d+)' '$dollars dollars and $cents cents'
2018-12-24 03:57:36 +00:00
123 dollars and 45 cents
```
2018-12-26 00:01:11 +00:00
In the unlikely case you stumble upon ambiguities, resolve them by using `${var}` instead of `$var` . Here's an example:
2018-12-24 03:57:36 +00:00
```sh
2018-12-26 19:23:13 +00:00
> echo '123.45' | sd '(?P<dollars>\d+)\.(?P<cents>\d+)' '$dollars_dollars and $cents_cents'
2018-12-24 03:57:36 +00:00
and
2019-06-18 18:07:02 +00:00
2018-12-26 19:23:13 +00:00
> echo '123.45' | sd '(?P<dollars>\d+)\.(?P<cents>\d+)' '${dollars}_dollars and ${cents}_cents'
2018-12-24 03:57:36 +00:00
123_dollars and 45_cents
```
2018-12-26 00:01:11 +00:00
4. **Find & replace in a file**
2018-12-24 03:57:36 +00:00
```sh
2019-06-18 18:07:02 +00:00
> sd 'window.fetch' 'fetch' http.js
2018-12-24 03:57:36 +00:00
```
2018-12-26 00:01:11 +00:00
That's it. The file is modified in-place.
2018-12-24 03:57:36 +00:00
2019-06-18 18:07:02 +00:00
To preview changes:
2018-12-24 03:57:36 +00:00
```sh
2019-06-18 18:07:02 +00:00
> sd -p 'window.fetch' 'fetch' http.js
2018-12-24 03:57:36 +00:00
```
2018-12-24 04:01:20 +00:00
5. **Find & replace across project**
2018-12-24 03:57:36 +00:00
2019-04-01 12:29:18 +00:00
This example uses [fd ](https://github.com/sharkdp/fd ).
2018-12-24 03:57:36 +00:00
Good ol' unix philosophy to the rescue.
```sh
2019-06-18 18:07:02 +00:00
sd 'from "react"' 'from "preact"' $(fd -t f)
2018-12-24 03:57:36 +00:00
```
Same, but with backups (consider version control).
```bash
for file in $(fd -t f); do
cp "$file" "$file.bk"
2019-06-18 18:07:02 +00:00
sd 'from "react"' 'from "preact"' "$file";
2018-12-24 03:57:36 +00:00
done
```