Easy sed
- Repo: https://github.com/pepa65/esed
- After: https://github.com/chmln/sd but changed the CLI
- License: MIT
- Small standalone single binary in 100% Rust
Why use it over any existing tools?
- Painless regular expressions -
eseduses Rust regex syntax that is very feature-rich. Forget about dealing with quirks ofsedorawkand get productive immediately. - Easy to read, easy to write - Search & replace expressions are split up, which makes them easy to read and write. No more messing with unclosed and escaped slashes.
- Optional String-literal mode - Non-regex search & replace: no more backslashes, no more remembering which characters are special and need to be escaped.
- Easy to modify multiple files - No for-loops needed, just list all files or use shell-wildcards.
- Smart, common-sense defaults - Defaults follow common sense and are tailored for typical daily use.
- Still need
-i/--in-placeto modify files - Defaults to output to stdout for safety.
While sed does a whole lot more, esed focuses on doing just one thing and doing it well.
Here are some cherry-picked examples where esed shines:
- Simpler syntax for replacing all occurrences:
- esed:
esed SEARCH REPLACEMENT - sed:
sed s/SEARCH/REPLACEMENT/g
- esed:
- Replace newlines with comma-space:
- esed:
esed '\n' ', ' - sed:
sed ':a;N;$!ba;s/\n/, /g'
- esed:
- Extracting stuff surrounded ny slashes out of string:
- esed:
echo "Example with /path/, etc." |esed '.*(/.*/)' '$1' - sed:
echo "Example with /path/, etc." |sed -E 's/.*(\\/.*\\/)/\1/g' - Or
sedwith a different delimiter (still messy):echo "Example with /path/ etc." |sed -E 's@.*(/.*/)@\1@g'
- esed:
- In place modification of file:
- esed:
esed -i SEARCH REPLACEMENT FILE - sed:
sed -i -e 's/SEARCH/REPLACEMENT/g' FILE(some platforms do not need the-e)
- esed:
- Replacement in multiple files:
- esed:
esed -i SEARCH REPLACEMENT FILE* XFILE STHFILE - sed:
for file in FILE* XFILE STHFILE; do sed -i -e 's/SEARCH/REPLACEMENT/g' "$file"; done
- esed:
- Common regex features like start & end anchors
^ #, dot.wildcard (unicode) character matching, character class matching between square brackets[ ]and character exclusion between[^and]. - Quantifiers (after the pattern):
*: 0 or more,+: 1 or more,?: 0 or 1,{n,m}: between n and m times - Alternation:
|matches either the pattern on the left or the right. - Escape special characters (
. * & $) by prefixing with\\. In REPLACEMENT use$$to escape$. - Special matchings:
\s: whitespace,\S: non-whitespace,\d: digit,\pL: letter (any unicode class)\n: newline,\r: carriagereturn,\t: tab - Non-unicode matching between
(?-u:and) - Grouping between parentheses
( )enables capturing patterns in the replacement and combining/grouping. - Named grouping between
(?P<NAME>and). - For full documentation, see: https://docs.rs/regex/latest/regex/#syntax
hyperfine --warmup 3 --export-markdown out.md \
'sed -E "s/\"/'"'"'/g" *.json >/dev/null' \
'sed "s/\"/'"'"'/g" *.json >/dev/null' \
'esed -i "\"" "'"'"'" *.json >/dev/null'| 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 |
esed "\"" "'" *.json > /dev/null |
0.997 ± 0.006 | 0.987…1.007 |
Result: ~2.35 times faster
hyperfine --warmup 3 --export-markdown out.md \
'sed -E "s:(\w+):\1\1:g" dump.json >/dev/null' \
'sed "s:\(\w\+\):\1\1:g" dump.json >/dev/null' \
'esed "(\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.1.4…11.725 |
sed "s:\(\w\+\):\1\1:g" dump.json >/dev/null |
11.239 ± 0.208 | 11.057…11.762 |
esed "(\w+)" "$1$1" dump.json >/dev/null |
0.942 ± 0.004 | 0.936…0.951 |
Result: ~11.93 times faster
wget https://github.com/pepa65/esed/releases/download/1.1.4/esed
sudo mv esed /usr/local/bin
sudo chown root:root /usr/local/bin/esed
sudo chmod +x /usr/local/bin/esed
If not installed yet, install a Rust toolchain, see https://www.rust-lang.org/tools/install
The binary esed will be installed into ~/.cargo/bin/ (might need to be added to PATH!)
cargo install esed
cargo install --git https://github.com/pepa65/esed
git clone https://github.com/pepa65/esed
cd esed
rustup target add x86_64-unknown-linux-musl
cargo inst # Alias defined in .cargo/config.toml
Even without a full Rust toolchain, rust binaries can be installed with the static binary cargo-binstall:
# Install cargo-binstall for Linux x86_64
# (Other versions are available at https://crates.io/crates/cargo-binstall)
wget github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
tar xf cargo-binstall-x86_64-unknown-linux-musl.tgz
sudo chown root:root cargo-binstall
sudo mv cargo-binstall /usr/local/bin/
Only a linux-x86_64 (musl) binary available: cargo-binstall esed
This will also be installed into ~/.cargo/bin/ and might need to be added to PATH!
- String-literal mode: By default, expressions are treated as regex. Use
-s/--stringsto disable regex.# Remove string with special characters echo 'lots((([]))) of special chars' |esed -s '((([])))' '' # Result: "lots of special chars"
- Basic regex use: Trim trailing whitespace.
# Trim whitespace before the end of input echo 'lorem ipsum 23 ' |esed '\s+$' '' # Result: "lorem ipsum 23"
- Capture groups: Can be indexed (numbered) or named
- Indexed capture groups:
echo 'cargo +nightly watch' |esed '(\w+)\s+\+(\w+)\s+(\w+)' 'cmd: $1, channel: $2, subcmd: $3' # Result: "cmd: cargo, channel: nightly, subcmd: watch"
- Named capture groups:
If stumbling upon ambiguities, use
echo "123.45" |esed '(?P<dollars>\d+)\.(?P<cents>\d+)' '$dollars dollars and $cents cents' # Result: 123 dollars and 45 cents
${var}instead of$var, like:echo '123.45' |esed '(?P<dollars>\d+)\.(?P<cents>\d+)' '${dollars}_dollars and ${cents}_cents' # Result: "123_dollars and 45_cents"
- Indexed capture groups:
- Search & replace in a file
To preview changes (the
esed -i 'window.fetch' 'fetch' http.js
-i/--in-placeflag needs to be given to modify files):esed 'window.fetch' 'fetch' http.js
- Search & replace across project
esed 'from "react"' 'from "preact"' ../*/*.json
esed v1.1.4 - Easy sed
Usage: esed [OPTIONS] <SEARCH> <REPLACEMENT> [FILE]...
Arguments:
<SEARCH> SEARCH: regexp or (if using '-s'/'--strings') string
<REPLACEMENT> REPLACEMENT (regex captured values like $1 can be used)
[FILE]... Optional path(s) to file(s)
Options:
-i, --in-place Modify file(s) in-place instead of output to stdout
-s, --strings Treat SEARCH and REPLACEMENT as literal strings [default: regex]
-c, --case-insensitive Use regex case-insensitive [default: case-sensitive]
-w, --word Match the SEARCH regex as a full word [default: match anywhere]
-d, --dot-match-newline Dots also match newlines [default: newlines not matched by dots]
-n, --no-multiline No multiline: '^' and '$' anchor only to the very beginning and end [default: multiline]
-l, --limit <MAX> Limit the number of replacements per file (0: unlimited) [default: 0]
-h, --help Print help
-V, --version Print version