TIL: save half-typed commands in bash and zsh

TL;DR

  • Bash: Ctrl-u, execute another command, Ctrl-y.
  • Zsh: same as bash, better: Ctrl-q, best: widget push-line-or-edit.

zsh push-line-or-edit

Gory details, bash it up

I was pairing on a colleague’s machine today. He hadn’t had time to configure his environment yet and it was running vanilla bash. Halfway through typing a long command, meticulously assembled from different places, we realized that there’s another command we should run before this one.

We had the following four options:

  1. Open a new terminal and run the command there. We’d lose the current working directory and any custom environment variables. The former can be alleviated by using a terminal multiplexer (like screen or tmux), which usually share the working directory with new terminals. The latter would have been particularly annoying since we had authentication tokens set up via environment variables. Thanks, but no thanks.

  2. Use the mouse to select the command line, copy it to the pasteboard, type Ctrl-c to abort and return to a fresh prompt, execute whatever command we had forgotten earlier, and finally paste the previous line from the pasteboard. This becomes more complicated when using a terminal multiplexer, since mouse selection will probably span across multiple panes. For example with two vertical panes and the command wrapping onto a second line. Not much easier than the above, so no thanks.

  3. Move to the front of the command line with Ctrl-a, type # and execute with Enter. Or even faster, all steps in one shortcut: Esc-#. The line is now a comment, essentially not executing anything but still committing it to history. After executing whatever we had forgotten earlier, we’d retrieve the previous line from history with arrow-up, move to the front of the line with Ctrl-a, delete the # and continue with the editing. Works, but still cumbersome.

  4. Kill backward from the cursor to the beginning of the current line with Ctrl-u (or forward till end of line with Ctrl-k, depending on cursor location). The text is now deleted (“killed” in command line editing lingo) but still present in the shell’s pasteboard (“kill-ring”). After executing whatever we had forgotten earlier, we’d paste the killed command into a fresh prompt with Ctrl-y (“yanking”). OK, now that sounds feasible.

There’s yet a slightly more convenient way, but requires customizing bash. Instead of killing the line with either Ctrl-u or Ctrl-k depending on cursor location, or even having to move the cursor to beginning or end of line beforehand, the whole line can be killed with the command called kill-whole-line. Bind it to Ctrl-u by adding the following line to ~/.inputrc:

"\C-u": kill-whole-line

Either way, we always lose the original cursor position after the line is restored on a fresh prompt. It will be positioned at the end of the yanked line.

What about zsh?

Zsh users are a little luckier in this regard. For them the two-step dance comes in a single zle widget (refer to my previous post to learn more about zle and zle widgets). Not only that, but they have three widgets to choose from: push-line, push-input and push-line-or-edit, roughly ordered by usefulness. The widgets are called “push” because the underlying data structure is a stack.

Here are the differences (in a nutshell, see docs linked below for what really happens under the hood):

  • push-line: kills the line and yanks it on the next fresh prompt. Does only operate on the current line in a multiline command.

  • push-input: kills the whole, potentially multiline, command and yanks it on the next fresh prompt. It comes with the nice side effect that a multiline command becomes editable again for all lines. In vanilla zsh this is bound to Ctrl-q (along with esoteric Esc-Q and Esc-q).

  • push-line-or-edit: works like push-line outside a multiline command. Inside a multiline command, it first makes all lines editable (imagine push-input with an empty follow-up command) and subsequently behaves like push-input giving a new prompt. See the animated example above for a demo.

In my opinion push-line-or-edit is the most useful of the three and I like it to override the default Ctrl-q binding. This is achieved by adding the following to the zsh config:

bindkey '^q' push-line-or-edit

As opposed to bash, all three zle widgets restore the cursor position after yanking the command on a fresh prompt.

Resources:


More posts here.

Comments