The way zsh handles keyboard input can be completely customized.
However, often users run into problems, when their terminal emulator sends charactersequences for special keys, that zsh doesn’t know about by default.

If you’re here, because your special keys, like HOME, DEL etc. don’t work. Use the terminfo solution presented on this page. It will work with almost all terminals on all systems, that provide terminfo (even NetBSD has that these days). And without any user-intervention.

See EmacsAndViKeys for keybinding nirvana.
zkbd – special keys solution

If you use several different terminal emulators, it’s likely, that you’ve run into the problem of neil, that pressing a special key like PageDown will just display a tilde instead of doing what it’s supposed to.

There is a function described in zshcontrib(1) that reads and stores keydefinitions for special keys, if it recognizes a terminal, it hasn’t seen before.
This is a snippet, that shows how it could be used:

autoload zkbd
function zkbd_file() {
[[ -f ~/.zkbd/${TERM}-${VENDOR}-${OSTYPE} ]] && printf ‘%s’ ~/”.zkbd/${TERM}-${VENDOR}-${OSTYPE}” && return 0
[[ -f ~/.zkbd/${TERM}-${DISPLAY} ]] && printf ‘%s’ ~/”.zkbd/${TERM}-${DISPLAY}” && return 0
return 1

[[ ! -d ~/.zkbd ]] && mkdir ~/.zkbd
if [[ ${ret} -ne 0 ]]; then
if [[ ${ret} -eq 0 ]] ; then
source “${keyfile}”
printf ‘Failed to setup keys using zkbd.\n’
unfunction zkbd_file; unset keyfile ret

# setup key accordingly
[[ -n “$key[Home]” ]] && bindkey — “$key[Home]” beginning-of-line
[[ -n “$key[End]” ]] && bindkey — “$key[End]” end-of-line
[[ -n “$key[Insert]” ]] && bindkey — “$key[Insert]” overwrite-mode
[[ -n “$key[Backspace]” ]] && bindkey — “$key[Backspace]” backward-delete-char
[[ -n “$key[Delete]” ]] && bindkey — “$key[Delete]” delete-char
[[ -n “$key[Up]” ]] && bindkey — “$key[Up]” up-line-or-history
[[ -n “$key[Down]” ]] && bindkey — “$key[Down]” down-line-or-history
[[ -n “$key[Left]” ]] && bindkey — “$key[Left]” backward-char
[[ -n “$key[Right]” ]] && bindkey — “$key[Right]” forward-char

reading $terminfo[]

To get special keys working, you can also try to ask the terminfo database for the actual key sequences; this requires a valid terminfo database for the terminal in question, but works in most cases and requires no user interaction.

The following snippet assigns data from the $terminfo[] array to a $key[] hash, that is compatible to the hash created by zkbd (see above). That makes it possible to switch back and forth between the zkbd solution and the terminfo solution in case something goes wrong.

# create a zkbd compatible hash;
# to add other keys to this hash, see: man 5 terminfo
typeset -g -A key


# setup key accordingly
[[ -n “$key[Home]” ]] && bindkey — “$key[Home]” beginning-of-line
[[ -n “$key[End]” ]] && bindkey — “$key[End]” end-of-line
[[ -n “$key[Insert]” ]] && bindkey — “$key[Insert]” overwrite-mode
[[ -n “$key[Backspace]” ]] && bindkey — “$key[Backspace]” backward-delete-char
[[ -n “$key[Delete]” ]] && bindkey — “$key[Delete]” delete-char
[[ -n “$key[Up]” ]] && bindkey — “$key[Up]” up-line-or-history
[[ -n “$key[Down]” ]] && bindkey — “$key[Down]” down-line-or-history
[[ -n “$key[Left]” ]] && bindkey — “$key[Left]” backward-char
[[ -n “$key[Right]” ]] && bindkey — “$key[Right]” forward-char

# Finally, make sure the terminal is in application mode, when zle is
# active. Only then are the values from $terminfo valid.
if (( ${+terminfo[smkx]} )) && (( ${+terminfo[rmkx]} )); then
function zle-line-init () {
echoti smkx
function zle-line-finish () {
echoti rmkx
zle -N zle-line-init
zle -N zle-line-finish

CTRL-V – manually find out esc.-sequences

If you don’t like automation like zkbd provides, you can get the keysequences like this:
In order to see the sequence to give to bindkey for the key you want, use quoted-insert (control-V) and press the key combination. Another way to do this is to `cat > /dev/null` and then press the key combination.

So, in order to get bind something to your keyboard’s delete key, you do:

% bindkey ” delete-char

That means you *literally* hit control-v followed by delete. The angle-brackets are there for the sake of readability. You DO NOT HAVE TO INCLUDE THEM, in order to get the proper sequence. Also note, that the single-quotes are probably needed, do not leave them out.
Why isn’t Control-R working (anymore)?

You are probably in vi-Mode, because you have set $EDITOR or $VISUAL to something starting with ‘vi’. And this question sounds a lot like you do not want that. 🙂

The quick fix is to simply switch to emacs-like keybindings via bindkey -e.

However, if you are sure that you want vi-bindings, issue the following commands to enable ‘history-incremental-search-backward’ in both vi-keytables:

bindkey -M viins ‘^r’ history-incremental-search-backward
bindkey -M vicmd ‘^r’ history-incremental-search-backward

Swapping Keys

If you would like to swap keys inside zsh (like `~), i.e. swap keys X and Y so typing X gives Y and vice versa; check this post from Peter Stevenson:

Link: ZU#9947 (IMO the function is a bit too long for this page)
Most Wanted

Here are a few things, that users miss from time to time.

One of the things I most missed about bash when first switching to Zsh was that M-b and M-f (backward-word and forward-word) would jump over an entire /path/location, rather than to each word separated by a ‘/’. To make the behavior more like bash, you can evaluate this command:

export WORDCHARS=”

If you prefer, you can make WORDCHARS local to the definition of say, backword-word, so that kill-word still deletes an entire path. To do this, you need to define a widget function. For example, for backward-word, I use this:

tcsh-backward-word() {
zle backward-word
zle -N tcsh-backward-word

PiyoPiyo: This is what I use in my own setup:

# by default: export WORDCHARS=’*?_-.[]~=/&;!#$%^(){}<>’
# we take out the slash, period, angle brackets, dash here.
export WORDCHARS=’*?_[]~=&;!#$%^(){}’

A question about binding keys: The one emacs keybinging I really miss in zsh is Ctrl-LeftArrowKey? and Ctrl-RightArrowKey? as backward-word and forward-word. I can’t seem to find out if it is possible to duplicate these keybindings in zsh. Is the terminal able to distinguish between LeftArrowKey? and Ctrl-LeftArrowKey?? It doesn’t seem to be from my investigations. Has anybody done this? Can it be done? – Merc
This appears to work:

bindkey ‘^[[5D’ emacs-backward-word
bindkey ‘^[[5C’ emacs-forward-word

This works: – czo

My xterm (KDE Konsole) was showing “;5D” and “;5C”, so I just used them instead:

bindkey ‘;5D’ emacs-backward-word
bindkey ‘;5C’ emacs-forward-word

on my urxvt it works with -/-

bindkey “\e\e[D” backward-word
bindkey “\e\e[C” forward-word

– phelix

If you see such a key sequence, I would try using it before a custom remapping as described below.

a terminal has no knowledge of a Ctrl-Arrow keypress, BUT if you use a terminal emulator (like xterm or rxvt under X11) you can assign an X keyboard event to a string sequence like ‘^[[5D’ that you then use in bindkey. Here is a line I have in my .Xresources

XTerm*vt100.Translations: #override\n\
Ctrl Left : string(“\033[90~”) \n\
Ctrl Right : string(“\033[91~”) \n

I have remapped shift-enter (using loadkeys for the console, and xmodmap in X) to produce Ctrl-J and added

bindkey “^J” self-insert

to my .zshrc. Thus I can type multiline command lines, and still be able to move the cursor up/down between the lines while editing. (This also works quite well with Ctrl-A and Ctrl-E, which if they’re at the beginning/end of a line jumps to the beginning/end of the previous/next line.) – Zrajm

You might also enjoy: