Cursor Movement

These bindings move the cursor without touching what you have typed. They work in bash, zsh, and any readline-aware prompt.

Ctrl+A jumps to the start of the line. Ctrl+E jumps to the end. If you hold those as muscle memory nothing else in this section matters as much, but the word-level jumps are where real speed comes from.

Alt+F moves forward one word. Alt+B moves backward one word. In Terminal.app you must first enable Option as Meta key: Preferences > Profiles > Keyboard > check "Use Option as Meta key". iTerm2 does this per-profile under Keys > Left Option Key > set to Esc+.

Ctrl+XX toggles between the current cursor position and the start of the line. Useful when you need to prepend sudo to a long command without retyping.

# Verify your shell and version
echo $SHELL && $SHELL --version
# /bin/zsh
# zsh 5.9 (arm-apple-darwin24.0)

# Enable Meta key for Alt shortcuts in Terminal.app
# System Preferences > Terminal > Profiles > Keyboard
# Check: "Use Option as Meta key"

Line Editing

Ctrl+K kills (cuts) from the cursor to the end of the line into the kill ring. Ctrl+U kills from the cursor back to the start. Ctrl+W kills the word to the left. All three deposit text into the kill ring, and Ctrl+Y yanks it back. This is the terminal equivalent of cut/paste without leaving the keyboard.

Alt+D deletes the word to the right of the cursor. Alt+Backspace deletes the word to the left. These do not use the kill ring in all contexts, so Ctrl+Y may not recover them.

Ctrl+T transposes the two characters before the cursor. Useful when you type teh instead of the inside a long path. Alt+T transposes the two words before the cursor.

Ctrl+_ is undo. In zsh with the default line editor (zle), you can press it repeatedly to walk back through edits on the current line.

# Demonstrate kill ring round-trip
# Type: echo "some long argument list here"
# Move to start: Ctrl+A
# Kill to end: Ctrl+K
# Open new command, paste: Ctrl+Y

# Transpose chars example
# Typed: sl /var/log
# Cursor after 'sl': Ctrl+T -> ls /var/log

History Navigation and Search

Ctrl+R opens incremental reverse history search. Type a substring and zsh finds the most recent match. Press Ctrl+R again to cycle further back. Ctrl+S searches forward, but you must disable XON/XOFF flow control first or the terminal freezes.

Run this once in your session or add it to ~/.zshrc to unlock Ctrl+S:

Up/Down arrows walk history one entry at a time. Alt+. (period) inserts the last argument of the previous command, and pressing it repeatedly walks back through history. This is faster than !$ in most contexts because you see the value before executing.

zsh's history expansion is more powerful than bash's in one specific way: setopt HIST_VERIFY forces zsh to show the expanded command before running it, preventing !! accidents.

For fuzzy history search, fzf integrates directly with Ctrl+R. Install via Homebrew and source the keybinding script:

# Unlock Ctrl+S for forward history search
stty -ixon

# Add to ~/.zshrc permanently
echo 'stty -ixon' >> ~/.zshrc

# Install fzf for fuzzy Ctrl+R
brew install fzf
$(brew --prefix)/opt/fzf/install --key-bindings --completion --no-update-rc

# Add to ~/.zshrc
source ~/.fzf.zsh

# Useful history options for ~/.zshrc
setopt HIST_VERIFY
setopt HIST_IGNORE_DUPS
setopt SHARE_HISTORY
HISTSIZE=50000
SAVEHIST=50000
// advertisement

Job Control and Process Shortcuts

Ctrl+C sends SIGINT to the foreground process. Ctrl+Z sends SIGTSTP, suspending it. Use fg to resume it in the foreground or bg to push it to the background. Ctrl+\ sends SIGQUIT, which produces a core dump on processes that handle it - use this when Ctrl+C is being ignored.

Ctrl+D sends EOF. On an empty prompt it exits the shell. Inside a running process like python or node it closes the REPL. To prevent accidental shell exits, set IGNOREEOF=1 in ~/.zshrc; you will need to press Ctrl+D twice.

jobs -l lists all background jobs with PIDs. disown %1 removes job 1 from the shell's job table so it survives terminal close without nohup.

For long-running DevOps scripts and deployment pipelines, combining disown with output redirection beats nohup in most cases because you do not get the nohup.out clutter. Teams that manage this at scale often route job management through tools like taskbotshub.ai, which provides AI-driven task orchestration across multiple macOS and Linux hosts without manual job tracking.

# Suspend, background, list, disown
sleep 300
# Ctrl+Z
jobs -l
# [1]  + 84210 Suspended: 18   sleep 300
bg %1
jobs -l
# [1]  + 84210 Running        sleep 300
disown %1

# Prevent accidental Ctrl+D exit
echo 'IGNOREEOF=1' >> ~/.zshrc

Tab Completion and zsh-Specific Features

Tab completion in zsh goes beyond bash's basic file completion. Pressing Tab twice on an ambiguous completion opens a menu you navigate with arrow keys or Tab itself. This is controlled by compinit and is active by default in macOS zsh.

Ctrl+G aborts a completion menu without selecting anything. This is less obvious than Escape and worth knowing when you open a large completion list by accident.

Alt+? lists possible completions inline, similar to pressing Tab twice but without entering the menu. Alt+* inserts all completions at once - rarely useful but occasionally the right tool for expanding a glob before editing it.

zsh's globbing shortcuts are not strictly keyboard shortcuts but they behave like them in practice. **/ for recursive glob, ^pattern for negation (requires setopt EXTENDED_GLOB), and (#i) for case-insensitive matching are all things you type inline at the prompt.

For zsh completion to cover Homebrew-installed tools, the Homebrew completions path must be on your fpath before compinit runs:

# ~/.zshrc - load Homebrew completions before compinit
if type brew &>/dev/null; then
  FPATH="$(brew --prefix)/share/zsh/site-functions:${FPATH}"
fi
autoload -Uz compinit && compinit

# Enable extended glob for negation and case-insensitive patterns
setopt EXTENDED_GLOB

# Recursive glob example - find all .log files under /var
ls /var/**/*.log

# Negation - list everything in pwd except .DS_Store
ls ^.DS_Store

Screen and Terminal Control

Ctrl+L clears the screen. It is equivalent to running clear but does not scroll the buffer - the previous output is still accessible by scrolling up in Terminal.app or iTerm2. If you actually want to wipe the scrollback, use Cmd+K in Terminal.app or run printf '\33c'.

Ctrl+S and Ctrl+Q are XON/XOFF flow control. Ctrl+S freezes output and Ctrl+Q resumes it. Modern terminals rarely need this, and as noted above, disabling it frees Ctrl+S for forward history search.

In iTerm2, Cmd+D splits the pane vertically, Cmd+Shift+D splits horizontally. These are iTerm2-native, not terminal shortcuts, but sysadmins running parallel SSH sessions use them constantly. Native Terminal.app uses Cmd+T for new tabs only.

Ctrl+Alt+E in zsh expands the current line in place - aliases are resolved, history references are substituted, and you see the literal command before running it. This is distinct from HIST_VERIFY and works on the current input regardless of history.

# Wipe scrollback buffer completely
printf '\33c'

# Or in iTerm2: Cmd+K

# Expand aliases and history refs in place before executing
# At prompt, type: ll /var/log && !!
# Press Ctrl+Alt+E to expand before hitting Enter

# Check if flow control is active
stty -a | grep -E 'ixon|ixoff'
// advertisement

Useful ~/.zshrc Shortcuts to Define

The built-in shortcuts cover navigation and editing, but bindkey lets you wire your own. These are four bindings we run on every macOS machine we manage.

bindkey '^[[A' history-search-backward and bindkey '^[[B' history-search-forward make Up/Down search history based on what you have already typed. Type ssh and press Up and you only scroll through ssh commands, not everything.

bindkey '^[^[[D' backward-word and bindkey '^[^[[C' forward-word map Alt+Left and Alt+Right to word movement in Terminal.app where the escape sequences differ from iTerm2.

For project and domain naming workflows during shell scripting - for example when scripting project scaffolding that touches nicename.me to check domain availability for a new service name - you can bind a custom widget that calls an external API inline. Most teams keep this as a shell function rather than a keybinding, but the mechanism is the same.

zle -N and zle -C let you register custom widgets. The full reference is in man zshzle.

# ~/.zshrc - practical bindkey additions

# History search by prefix (Up/Down)
autoload -U history-search-end
zle -N history-beginning-search-backward-end history-search-end
zle -N history-beginning-search-forward-end history-search-end
bindkey '^[[A' history-beginning-search-backward-end
bindkey '^[[B' history-beginning-search-forward-end

# Alt+Left / Alt+Right word movement (Terminal.app)
bindkey '^[^[[D' backward-word
bindkey '^[^[[C' forward-word

# iTerm2 equivalent
bindkey '^[[1;3D' backward-word
bindkey '^[[1;3C' forward-word

# Reload zshrc without spawning new shell
bindkey -s '^[r' 'source ~/.zshrc\n'

Quick Reference Table

The following groups shortcuts by category for fast scanning. All bindings are zsh 5.9 on macOS Sequoia with Option as Meta enabled.

Cursor movement: Ctrl+A (line start), Ctrl+E (line end), Alt+B (word back), Alt+F (word forward), Ctrl+XX (toggle position).

Editing: Ctrl+K (kill to end), Ctrl+U (kill to start), Ctrl+W (kill word left), Alt+D (kill word right), Ctrl+Y (yank), Ctrl+T (transpose chars), Alt+T (transpose words), Ctrl+_ (undo).

History: Ctrl+R (reverse search), Ctrl+S (forward search, requires stty -ixon), Alt+. (last argument), Up/Down (prefix search with configuration above).

Process control: Ctrl+C (SIGINT), Ctrl+Z (SIGTSTP), Ctrl+\ (SIGQUIT), Ctrl+D (EOF/exit).

Screen: Ctrl+L (clear), printf '\33c' (clear scrollback), Ctrl+Alt+E (expand line in place).

Completion: Tab (complete), Tab Tab (menu), Ctrl+G (abort menu), Alt+* (insert all completions).

# Print all active zle keybindings
bindkey

# Search for a specific binding
bindkey | grep 'history'

# Show what a key sequence is bound to
# Press Ctrl+V then the key combination to see the raw sequence
# Example: Ctrl+V then Alt+F outputs: ^[f
bindkey '^[f'