// ZSH-SUDO — TOGGLE sudo ON THE CURRENT COMMAND LINE

zsh plugin · single ZLE widget · prepend sudo if missing · strip sudo (and builtin / command / env wrappers) if present · recall last command from history if empty

GitHub Issues
// Color scheme

>_ZSH-SUDO

One keybind toggles sudo on the current command line. Sudo absent — prepend it. Sudo already there — strip it along with builtin / command / env and their args. Empty line — pull the last command from history and slap sudo on it.

Install

# Zinit
zinit light MenkeTechnologies/zsh-sudo

# Oh My Zsh
git clone https://github.com/MenkeTechnologies/zsh-sudo \
    "${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-sudo"
# then: plugins+=(zsh-sudo)

# Manual
git clone https://github.com/MenkeTechnologies/zsh-sudo
source zsh-sudo/sudo.plugin.zsh

Key binding

Wire the widget to whatever combo you prefer:

bindkey '^N' sudo-command-line     # Ctrl-N
# or the classic Esc-Esc pattern:
bindkey '\e\e' sudo-command-line

Configuration

Override the prepended command (e.g. doas) by setting both variables to the same string before the plugin loads:

export ZPWR_SUDO_REGEX='doas'
export ZPWR_SUDO_CMD='doas'

ZPWR_SUDO_CMD is prepended; ZPWR_SUDO_REGEX is what the parser hunts for when stripping. Both default to sudo and are only set when unset, so they never clobber a user override.

The two vars are decoupled so the strip side can use alternation while the prepend side picks one canonical tool. Strip either sudo or doas but always prepend sudo:

export ZPWR_SUDO_CMD='sudo'
export ZPWR_SUDO_REGEX='sudo|doas'
ZPWR_SUDO_CMDString prepended when there's nothing to strip. Default sudo.
ZPWR_SUDO_REGEXRegex fragment the strip side hunts for. Default sudo.

State table

Three behaviours, decided in this order: empty line → history recall; line already has a sudo prefix → strip; otherwise → prepend.

apt updatesudo apt update
sudo apt updateapt update
(empty line)sudo <last command from history>

The regex engine handles quoted commands, builtin/command prefixes, env with flags, leading variable assignments, and stacked sudo options (-u root -E, etc).

Worked examples

Every row below corresponds to a pinned test case in tests/t-sudo-command-line.zsh — the toggle is verified, not hypothetical.

ls -lasudo ls -la
sudo -u root lsls (value flag dropped)
sudo -EH lsls (stacked boolean flags)
FOO=bar BAZ=qux sudo lsls (leading var-assignments dropped)
command sudo lsls (wrapper word dropped)
builtin command sudo lsls
sudo env -i bashbash (env wrapper dropped)
sudo sudo sudo lsls (stacked sudo collapsed)
\\sudo lsls (backslash-quoted sudo)
  sudo ls  ls (leading whitespace preserved)
sudoers lssudo sudoers ls (no false strip)

The strip path only fires when a real sudo token is present — a command word that merely starts with sudo (like sudoers) is left alone and gets sudo prepended instead.

Cursor position

ZLE splits the command line at the cursor into $LBUFFER (left of cursor) and $RBUFFER (right of cursor). The widget tries the strip regex against $LBUFFER first, then falls through to $RBUFFER. Because the rewrite happens on whichever side matched — not the whole line — toggling works whether the cursor is at the end of the line, at column 0 (after ^A), or anywhere a clean sudo prefix sits on one side. Hit the chord twice and the line round-trips to its original state.

Notes

  • The entrypoint is sudo.plugin.zsh (not zsh-sudo.plugin.zsh) so oh-my-zsh's stock plugins/sudo auto-load can find it.
  • The plugin registers the widget with zle -N sudo-command-line but does not bind a key — the chord is yours to choose.
  • Empty-line recall uses builtin fc -ln -1, which prints the previous history entry with no line number and no trailing newline; builtin guards against a user-aliased fc.
  • The buffer is rewritten as inert string data, never eval'd: glob tokens stay literal, command substitutions are not run at toggle time, and multibyte UTF-8 commands round-trip unchanged.
  • No external dependencies — the widget runs entirely inside zsh's match engine. Identical behaviour on macOS, Linux, BSD, and WSL.

Troubleshooting

Chord does nothingYou haven't bound a key — the plugin ships no default. Add bindkey '^N' sudo-command-line.
doas not strippedSet ZPWR_SUDO_REGEX and ZPWR_SUDO_CMD before the plugin loads. The default regex matches only sudo.
Strip fails mid-linePlace the cursor at the start or end of the line. A cursor landing inside the sudo flag block splits the prefix across $LBUFFER/$RBUFFER so neither side matches and the fallthrough prepends another sudo.
Override ignoredThe env vars are read at source-time and only set when unset. Export them before sourcing the plugin; re-sourcing won't clobber an existing value.

Sibling plugins

Part of the MenkeTechnologies zsh plugin family — the MenkeTechnologiesMeta umbrella: