17

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

6
  • 1
    What do you mean by remapping to jk ? Is that some special key on your keyboard? Commented Aug 14, 2016 at 1:08
  • please see edit
    – mbigras
    Commented Aug 14, 2016 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 :-( Commented Aug 14, 2016 at 1:41
  • How are you starting your bash shell? Here is a good explanation of the Difference between .bashrc and .bash_profile Commented Aug 14, 2016 at 8:15
  • set -o vi goes in .bashrc, see Why doesn't my ~/.bash_profile work? Commented Aug 14, 2016 at 20:42

2 Answers 2

17

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:

13
  • 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
    Commented Aug 14, 2016 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
    Commented Aug 14, 2016 at 22:30
  • I definitely did it in zsh and not bash, is it not possible in bash?
    – mbigras
    Commented Aug 14, 2016 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
    Commented Aug 14, 2016 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
    Commented Jun 8, 2019 at 23:19
3

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
1
  • I came here looking for answer that works for binding the '^G' key to enter "normal mode" in vi-enabled zsh, and this is the only one that worked.
    – Dalker
    Commented Jan 11, 2020 at 14:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .