8

I'm using a fresh install of ubuntu 16.04, with the bash shell. There are two things that I want to do:

  1. Set up vi mode so I can have vim-like movements from the terminal
  2. exit insert mode by typing jk

I read in another post how this can be done with zsh, how can I do it with bash?

tl;dr

put bind '"jk":vi-movement-mode' into your .bashrc file after set -o vi :)

server@thinkpad:~$ tail -n 2 .bashrc
set -o vi
bind '"jk":vi-movement-mode'

please see @grochmal's answer for a more detailed explanation

  • 1
    What do you mean by remapping to jk ? Is that some special key on your keyboard? – Stephen Harris Aug 14 '16 at 1:08
  • please see edit – mbigras Aug 14 '16 at 1:37
  • For your first, .bashrc is typically called on every shell (eg if you shell out of vi) but .bash_profile is only called on login shells. So .bashrc is the right place for set -o vi. I don't have an answer for your revised 2nd question :-( – Stephen Harris Aug 14 '16 at 1:41
  • How are you starting your bash shell? Here is a good explanation of the Difference between .bashrc and .bash_profile – the_velour_fog Aug 14 '16 at 8:15
  • set -o vi goes in .bashrc, see Why doesn't my ~/.bash_profile work? – Gilles Aug 14 '16 at 20:42
8

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:

  • thanks for the detailed explanation. Would you be willing to illustrate the same process except for mapping jk instead of CapsLock? I have it set on my osx machine; however, I don't have it for the rest of the weekend and I can't remember how I did it :) – mbigras Aug 14 '16 at 22:08
  • @mbigras - Are you sure you did it in bash? Bash has no ambiguos maps. I know of a way of doing it in zsh: bindkey -v; bindkey -s jk \\e. But that is something that will come back and bite you back when you need jk, because the timer is not configurable and you have no <leader> in zsh. – grochmal Aug 14 '16 at 22:30
  • I definitely did it in zsh and not bash, is it not possible in bash? – mbigras Aug 14 '16 at 23:18
  • @mbigras - Wait, I was wrong! Reading man bash I found bind that work pretty similar to zsh's bindkey. I'll update the answer. There you go, I'm using zsh too much. – grochmal Aug 14 '16 at 23:52
  • 2
    @Jason - Hey, this is the Vi SE, In here everyone writes an ALL CAPS word with: "type word"<Esc>viwU :). No completion needed – grochmal Jun 8 at 23:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.