wcarroll wcarroll - 3 months ago 20
Bash Question

zsh bindkey with history

In short I'd like to map

Ctrl + 2
to the second most recently logged command in my .zsh_history file. Normally I would type
!-2
to achieve this. However, to recreate this behavior in a shell function I wrote the following:

function two_back {
HISTFILE=~/.zsh_history
command=$(history | tail -n 2 | head -n 1 | cut -c 8-)

echo -n "$command"
}


This outputs the value that I would expect, so it seems fine to me. In fact, If I just run the function
two_back
and press
<Enter>
it works as expected. The issue seems to reveal itself after I bind the function to some keystrokes.

First I convert the function into a zsh widget, so that I can bind it to some keystrokes...

$ zle -N two_back_widget two_back


Then I use the
bindkey
command to create the mapping...

$ bindkey '^@' two_back_widget


Now when I type
Ctrl + 2
my z-shell line editor
zle
contains the output that I expect. But when I attempt to run the command by pressing
<Enter>
nothing happens... Can anyone else reproduce this? And does anyone know why this isn't working?

Answer

The reason this is not working is, that while the command was printed to the terminal (output) it was not written in the terminal (input).

In order to actually enter the command like it has been typed into the shell this should work:

function two_back {
   HISTFILE=~/.zsh_history
   command=$(history | tail -n 2 | head -n 1 | cut -c 8-)

   BUFFER="$command"
   CURSOR=$#BUFFER
}

zle -N two_back_widget two_back
bindkey '^@' two_back_widget

Explanation:

  • Within zle widgets BUFFER contains the current command line. It can also be modified. In this case BUFFER is set to the value of command. Writing to BUFFER effectively edits the current command on the command line.
  • CURSOR contains the cursor position on the command line. By setting it to the lengt of BUFFER the cursor will be placed at the end of the input. This is not strictly necessary, without setting it the cursor will remain in the same place. If the command line was previously empty the cursor will be on the very left of the command.