>_ZSH-SED-SUB
Rewrite the current command line in place. One ZLE widget bound to a single chord that does a global search-and-replace on the current buffer. Try it out with Ctrl-F Ctrl-P.
Install
# Zinit
zinit ice lucid nocompile
zinit load MenkeTechnologies/zsh-sed-sub
# Oh My Zsh
git clone https://github.com/MenkeTechnologies/zsh-sed-sub \
"$HOME/.oh-my-zsh/custom/plugins/zsh-sed-sub"
# then: plugins+=(zsh-sed-sub)
# Manual
git clone https://github.com/MenkeTechnologies/zsh-sed-sub
source zsh-sed-sub/zsh-sed-sub.plugin.zsh
Key bindings
Ctrl-F Ctrl-P | global sed-style substitution on the current buffer — prompts for pattern and replacement, rewrites BUFFER in place. Bound in the viins, vicmd, and emacs keymaps, so it fires regardless of bindkey -v / bindkey -e |
Usage
Press Ctrl-F Ctrl-P with a command on the line. A status banner appears:
Zsh-BsS: Extended Regex Sed Substitution (original>replaced):. Type the search pattern,
then a literal >, then the replacement, then Enter. The widget builds
s@original@replacement@g and runs it over the whole line with sed -E, so the
pattern is an extended regular expression and every match on the line is replaced.
The > character is the separator between the two halves. When you press > the
live preview flips colour (blue background → magenta background) so you always know which side you are
typing. If you press Enter without ever typing a >, the widget refuses with
Needed '>' for separation of original regex string and substitution! and leaves the line
untouched.
If the current line is empty or whitespace-only when you invoke the chord, the widget first runs
zle up-line-or-history to pull the previous command back onto the line, then prompts — so
"I just ran that, now fix the typo" is a single chord.
Editing keys (inside the prompt)
While typing the original>replacement expression, these keys are interpreted; every other
byte is appended to the expression:
| Enter | Commit. Split on the first >, build s@orig@replace@g, run sed -E, write the result back to BUFFER |
| Esc | Abort. Reset ANSI colours and return 1; the line is left exactly as it was |
| Backspace / Ctrl-H | Delete the last character of the expression (${sedArg[1,-2]}) |
| Ctrl-U | Clear the whole expression and start over without leaving the widget |
> | Separator between search and replacement; flips the preview colour to the replacement state |
Examples
In each case the command line already holds the "before" text; you press Ctrl-F Ctrl-P, type the
middle column, and press Enter to get the "after".
echo foo bar foo | foo>baz | → echo baz bar baz — every foo is replaced (the g flag is always on) |
ssh web01 uptime | web01>web02 | → ssh web02 uptime — re-target the last command at a different host |
cp /var/log/a /var/log/b | /var/log>/tmp | → cp /tmp/a /tmp/b — the @ sed delimiter means forward slashes need no escaping |
git checkout main | checkout>switch | → git switch main — swap a subcommand in place |
foo123 foo456 | foo[0-9]+>X | → X X — the pattern is a full sed -E extended regex |
How it works
The plugin ships one autoload function, basicSedSub (autoload/basicSedSub), registered
as a ZLE widget with zle -N basicSedSub. The function:
- runs
emulate -LR zshso the user'ssetoptsettings can't change its behaviour; - if
BUFFERis empty/whitespace, pulls the previous command withzle up-line-or-history; - reads the expression one keystroke at a time with
read -k, redrawing a live preview viazle -R; - requires a
>separator, then splits intoorig=${sedArg%%>*}andreplace=${sedArg##*>}; - escapes any literal
@in each half (${orig//@/\@}) so@can be used as the sed delimiter; - checks the pattern is present in the line (else
No Match.and abort); - rewrites the line:
BUFFER="$(print -r -- $BUFFER | sed -E -- "s@orig@replace@g")".
The entrypoint zsh-sed-sub.plugin.zsh only appends the autoload directory to fpath,
autoloads the function, registers the widget, and binds ^F^P in all three keymaps. No work runs at
shell-startup time beyond that — the widget body executes only when you press the chord.
Rebinding
The chord and the widget are independent. To move the binding off ^F^P:
# drop the default chord, bind your own bindkey -r '^F^P' bindkey '^X^S' basicSedSub # repeat for -M viins / -M vicmd as desired
The widget name (basicSedSub) never changes; only the key that triggers it does.
Requirements
- An interactive
zshwith ZLE (the widget reads keystrokes and rewritesBUFFER). sed— BSD or GNU; the-Eextended-regex flag is portable across both.
No other dependencies. The keystroke comparisons use zsh's numeric-character syntax ($((#x))),
which has been stable across the zsh 5.x line.
Troubleshooting
Nothing happens on Ctrl-F Ctrl-P | Another binding may own ^F^P. Check with bindkey '^F^P'; it should print basicSedSub. Re-source the plugin or rebind (see above). |
Needed '>' for separation... | You pressed Enter without a > in the expression. The format is pattern>replacement. |
No Match. | The search pattern doesn't occur in the current line, so nothing was changed. Check the regex (it is interpreted as sed -E). |
Replacing a literal @ | Supported — @ in either half is escaped automatically before it is used as the internal sed delimiter. |
For the full keystroke-by-keystroke flow, ANSI state machine, and test coverage, see the Engineering Report.
Sibling plugins
Part of the MenkeTechnologies zsh plugin family — the MenkeTechnologiesMeta umbrella:
- zsh-better-npm-completion — cache-aware
npm installcompletion - zsh-cargo-completion — live
cargo add/cargo installcompletion - zsh-cpan-completion — live MetaCPAN
cpan/cpanmcompletion - zsh-dotnet-completion — dotnet CLI completion
- zsh-expand — spacebar expansion (11,688 tests)
- zsh-gem-completion — ruby
gem installremote completion - zsh-git-acp — 159 git aliases + add/commit/push keybindings
- zsh-git-repo-cache — index every git repo on disk
- zsh-learn — MySQL-backed learning collection / quiz
- zsh-more-completions — 47,393-file mega completion corpus
- zsh-nginx — nginx commands + service-wrapper aliases
- zsh-pip-description-completion — pip remote completion with descriptions
- zsh-sudo — ESC ESC to prepend sudo
- zsh-xcode-completions — xcodebuild / xcrun / swift completion