In bash vi mode, map jk to exit insert mode

TL;DR

Bash has a similar functionality to zsh's bindkey through bind, but it does not have several vi modes like zsh. After set -o vi you can do:

bind '"jk":vi-movement-mode'

which is the equivalent of zsh's bindkey -M <all vi modes> jk vi-movement-mode

The vi-movement-mode functions comes from inputrc (see /etc/inputrc for a list of them).

Full text

As Stephen Harris points out in his comment:

  • .bashrc is called by bash always (and notably not by other shells).

  • .bash_profile is only called on login shells (and again, bash only).

Several distros come with a .bash_profile skeleton that looks as follows:

# ~/.bash_profile
[[ -f ~/.bashrc ]] && . ~/.bashrc

Which is a good content for .bash_profile since you can simply forget it exists.

Now, to map jk to Esc in the shell session, that is not really possible. When you do:

inoremap jk <esc>

In Vim, after you type j, Vim knows it needs to wait a little bit to see if you type k next and it should invoke the mapping (or that you type another key and the mapping should not be triggered). As an addendum this is controlled by :set timeoutlen=<miliseconds> in Vim (see :h timeoutlen).

Several shell's or X11 has no such timeout control and does not allow for multiple character mappings. Only a mapping of a single key is allowed (But see the support notes below.) .

set -o vi

Does not read .vimrc, it only imitates some vi (not even vim) key combinations that can be used in the shell. The same can be said about -o emacs, it does not come with the full power of emacs.


zsh support

zsh actually supports map timeout. And you can use the following to map jk to <esc>:

bindkey -v  # instead of set -o vi
bindkey -e jk \\e

(That will need to go to ~/.zshrc not ~/.bashrc)

Yet, I advise against this. I use vim and zsh most of the time. I have inoremap jk <esc> in my vimrc and I did try using the bindkey combination above. zsh waits too long to print j when using it, and that annoyed me a lot.


bash support

bash supports readline bind. I believe that bash can be compiled without readilne therefore there may be some rare systems that have bash that do not support bind (be watchful). To map jk to <esc> in bash you need to do:

set -o vi
bind '"jk":"\e"'

(yes that's a double level of quoting, it is needed)

Again, this makes typing j quite annoying. But somehow less annoying than the zsh solution on my machine (probably the default timeout is shorter).


Workaround (for non-bash and non-zsh shells)

The reason for remapping the Esc key is that it lies quite far away on the keyboard, and typing it takes time. A trick that can be borrowed from the emacs guys is to remap CapsLock since it is a useless key anyway. emacs guys remap it to Ctrl but we will remap it to Esc.

Let's use xev -event keyboard to check the keycode of CapsLock:

KeyPress event, serial 25, synthetic NO, window 0x1c00001,
    root 0x496, subw 0x0, time 8609026, (764,557), root:(765,576),
    state 0x0, keycode 66 (keysym 0xffe5, Caps_Lock), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

And to check the function of Esc:

KeyPress event, serial 25, synthetic NO, window 0x1c00001,
    root 0x496, subw 0x0, time 9488531, (571,525), root:(572,544),
    state 0x0, keycode 9 (keysym 0xff1b, Escape), same_screen YES,
    XLookupString gives 1 bytes: (1b) "
    XmbLookupString gives 1 bytes: (1b) "
    XFilterEvent returns: False

Very good, CapsLock is keycode 66 and Esc's function is called "Escape". Now we can do:

# diable caps lock
xmodmap -e "remove lock = Caps_Lock"
# make an Esc key from the keycode 66
xmodmap -e "keycode 66 = Escape"

The above must be done in this order. Now every time you hit CapsLock it works like an Esc key.


The tricky part is where to set this. A file ~/.Xmodmap with the content:

remove lock = Caps_Lock
keycode 66 = Escape

Should be respected by most distros (actually display managers, but I'm saying distros for simplicity), but I saw ones that don't respect several ~/X* files. For such distros you may try something like:

if [ "x" != "x$DISPLAY" ]; then
    xmodmap -e "remove lock = Caps_Lock"
    xmodmap -e "keycode 66 = Escape"
fi

In your .bashrc.

(In theory that would be better placed in ~/.xinitrc but if a display manager does not respect .Xmodmap it will definitely not respect ~/.xnintrc.)

Extra note: This only remaps CapsLock to Esc in a X11 session, therefore the map will only work in terminal emulators. Actual tty's will not see the map.

References and extra reading:

  • Disabling CapsLock
  • Very detailed answer on how key remapping in X11 works
  • .bashrc vs. .bash_profile

Thanks, for previous answers, I use this in my ~/.zshrc for vi-like shortcuts in my terminal. I hope it'll help someone.

bindkey -v
bindkey 'jk' vi-cmd-mode