// ZSH-SED-SUB — GLOBAL SEARCH-AND-REPLACE ON THE COMMAND LINE

zsh plugin · 1 ZLE widget · rewrite the current command line in place via sed-style substitution

GitHub Issues
// Color scheme

>_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-Pglobal 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:

EnterCommit. Split on the first >, build s@orig@replace@g, run sed -E, write the result back to BUFFER
EscAbort. Reset ANSI colours and return 1; the line is left exactly as it was
Backspace / Ctrl-HDelete the last character of the expression (${sedArg[1,-2]})
Ctrl-UClear 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 foofoo>bazecho baz bar baz — every foo is replaced (the g flag is always on)
ssh web01 uptimeweb01>web02ssh web02 uptime — re-target the last command at a different host
cp /var/log/a /var/log/b/var/log>/tmpcp /tmp/a /tmp/b — the @ sed delimiter means forward slashes need no escaping
git checkout maincheckout>switchgit switch main — swap a subcommand in place
foo123 foo456foo[0-9]+>XX 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:

  1. runs emulate -LR zsh so the user's setopt settings can't change its behaviour;
  2. if BUFFER is empty/whitespace, pulls the previous command with zle up-line-or-history;
  3. reads the expression one keystroke at a time with read -k, redrawing a live preview via zle -R;
  4. requires a > separator, then splits into orig=${sedArg%%>*} and replace=${sedArg##*>};
  5. escapes any literal @ in each half (${orig//@/\@}) so @ can be used as the sed delimiter;
  6. checks the pattern is present in the line (else No Match. and abort);
  7. 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 zsh with ZLE (the widget reads keystrokes and rewrites BUFFER).
  • sed — BSD or GNU; the -E extended-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-PAnother 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: