TIL: save half-typed commands in bash and zsh
Ctrl-u, execute another command,
- Zsh: same as bash, better:
Ctrl-q, best: widget
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:
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.
Use the mouse to select the command line, copy it to the pasteboard, type
Ctrl-cto 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.
Move to the front of the command line with
#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.
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-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
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-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
push-line-or-edit: works like
push-lineoutside a multiline command. Inside a multiline command, it first makes all lines editable (imagine
push-inputwith an empty follow-up command) and subsequently behaves like
push-inputgiving 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.