Time for a new instance of your favorite installment of this blog!
You probably know M-. to insert the last argument of the previous line. Sometimes, you want to insert a different argument. There are a few options: Use history expansion, e.g.
!:-2
for the third word on the line before (use TAB to expand it if you are not sure), or use M-. with a prefix argument: M-2 M-.Much nicer however is:
autoload -Uz copy-earlier-word zle -N copy-earlier-word bindkey "^[m" copy-earlier-word
Then, M-m will copy the last word of the current line, then the second last word, etc. But with M-. you can go back in lines too! Thus:
% echo a b c % echo 1 2 3 % echo <M-.><M-.><M-m> % echo b
Man, I wish I knew that earlier!
Sometimes, you want to combine a few previously typed lines into one:
% vi foo.c % gcc -o foo foo.c % gdb --args foo -v
Repeating these three commands all the time gets annoying (even if you know about C-o, it’s messy if you run other commands in between).
So, lets combine them into one line. Are you moving your hand to your mouse for copy&pasting? Bzzzt!
We can use history expansion as well:
% !-3<TAB>; !-2<TAB>; !-1<TAB> % vi foo.c; gcc -o foo foo.c; gdb --args foo -v
If you don’t see the numbers easily, you could do
!vi<TAB>
and so on. I always wanted to use C-r for this, however, thus I defined:autoload -Uz narrow-to-region function _history-incremental-preserving-pattern-search-backward { local state MARK=CURSOR # magick, else multiple ^R don't work narrow-to-region -p "$LBUFFER${BUFFER:+>>}" -P "${BUFFER:+<<}$RBUFFER" -S state zle end-of-history zle history-incremental-pattern-search-backward narrow-to-region -R state } zle -N _history-incremental-preserving-pattern-search-backward bindkey "^R" _history-incremental-preserving-pattern-search-backward bindkey -M isearch "^R" history-incremental-pattern-search-backward bindkey "^S" history-incremental-pattern-search-forward
Now C-r will work in a recursive way. It looks like this:
% <C-r>vi<RET>; % vi foo.c; <C-r>gcc % vi foo.c; >>gcc -o foo foo.c<< <RET>;<C-r>gdb % vi foo.c; gcc -o foo foo.c; >>gdb --args foo -v<< <RET> % vi foo.c; gcc -o foo foo.c; gdb --args foo -v
Since C-r is not very useful if you already typed something, I feel this redefinition is quite neat.
I have defined
$WORDCHARS
to exclude/
since I usually don’t want C-w or M-DEL to remove whole paths. But sometimes I do, thus:function _backward_kill_default_word() { WORDCHARS='*?_-.[]~=/&;!#$%^(){}<>' zle backward-kill-word } zle -N backward-kill-default-word _backward_kill_default_word bindkey '\e=' backward-kill-default-word # = is next to backspace
You probably heard of prompts like
: juno ~/src/zsh ;
(or just;
if you are hardcore-minimalist) which have the benefit that you can copy the whole line in your terminal emulator and just paste it to run it again:: juno ~/src/zsh ; grep -r bindkey . : juno ~/src/zsh ; : juno ~/src/zsh ; grep -r bindkey .
The
:
will nicely gobble up (almost) everything until the;
. But the paste keeps repeating the prompt, which is ugly, and I don’t like to have the:
and;
in my prompt really. That’s why I had the IMHO ingenious idea to set my prompt to:juno src/zsh%
… which actually is:
juno src/zsh%␣
That is, the last character is a Unicode non-breaking space (U+00A0). Which will look like a plain space and behave like one. Except that we can
bindkey
it to clear the input buffer:nbsp=$'\u00A0' bindkey -s $nbsp '^u'
Now, pasting the prompt will make it remove itself. Great!
Growing up with bash, I never was fond of zsh’s menu-select widget. I want TAB to complete as much as possible, and if I ever press TAB again, it should display the completions and just let me type on.
However, sometimes, I’d like to use the menu-select widget, e.g. if the files have prefixes that make selection hard (Maildirs, anyone?) or consist of weird special chars only.
It took me quite long to figure out how to enable menu-select for certain widgets only. The main problem was that the option
NO_ALWAYS_LAST_PROMPT
disables the menu widget. Thus, we have to unset it locally:zle -C complete-menu menu-select _generic _complete_menu() { setopt localoptions alwayslastprompt zle complete-menu } zle -N _complete_menu bindkey '^F' _complete_menu bindkey -M menuselect '^F' accept-and-infer-next-history bindkey -M menuselect '/' accept-and-infer-next-history bindkey -M menuselect '^?' undo bindkey -M menuselect ' ' accept-and-hold bindkey -M menuselect '*' history-incremental-search-forward
The latter keybindings make it a convenient file selector and browser (using / and DEL).
One thing that zsh lacks is the ability to start an interactive shell session from inside a shell script (e.g. rc(1) can do that). Sometimes you want to spawn a shell that runs a command, but is interactive after the program finished (e.g. when launched from urxvt or tmux).
Luckily, I found zshi. I have defined it as a script to allow execution from everywhere. It needs some support from the
.zshrc
:if [[ $1 == eval ]]; then shift ICMD="$@" set -- zle-line-init() { BUFFER="$ICMD" zle accept-line zle -D zle-line-init } zle -N zle-line-init fi
That solution is a bit more complex, but it allows you to press Up (or run
r
) to execute the command again.If you are watching series, you want to get to the next episode:
% mplayer foobar-S01-E23.mkv % <Up><C-x a> % mplayer foobar-S01-E24.mkv
zsh includes
incarg
, but it only works if you put the cursor on the number. This solution increments the last number, anywhere, and knows about zero padding:_increase_number() { local -a match mbegin mend [[ $LBUFFER =~ '([0-9]+)[^0-9]*$' ]] && LBUFFER[mbegin,mend]=$(printf %0${#match[1]}d $((10#$match+${NUMERIC:-1}))) } zle -N increase-number _increase_number bindkey '^Xa' increase-number bindkey -s '^Xx' '^[-^Xa'
I use Emacs keybindings, but sometimes I wish I had vi’s command mode. Luckily, it’s just a C-x C-v away in the default configuration! Heck, you may even go ahead and do:
bindkey '^[' vi-cmd-mode
… and
i
will put you back into Emacs mode again.A great anti-feature of history expansion is when it fails:
% a carefully constructed command line !?gcc !?vim !?quux zsh: no such event: gcc !<Up> % a carefully constructed command line im !?quux
And your history expanders are gone. Not so with this snippet:
function _recover_line_or_else() { if [[ -z $BUFFER && $CONTEXT = start && $zsh_eval_context = shfunc && -n $ZLE_LINE_ABORTED && $ZLE_LINE_ABORTED != $history[$((HISTCMD-1))] ]]; then LBUFFER+=$ZLE_LINE_ABORTED unset ZLE_LINE_ABORTED else zle .$WIDGET fi } zle -N up-line-or-history _recover_line_or_else function _zle_line_finish() { ZLE_LINE_ABORTED=$BUFFER } zle -N zle-line-finish _zle_line_finish
This will keep the last line in all cases, allowing you to fix it:
% a carefully constructed command line !?gcc !?vim !?quux zsh: no such event: gcc !<Up> % a carefully constructed command line !?gcc !?vim !?quux
Renaming long file names sucks. Many use graphical file managers for it. I use
imv
(interactive mv):imv() { local src dst for src; do [[ -e $src ]] || { print -u2 "$src does not exist"; continue } dst=$src vared dst [[ $src != $dst ]] && mkdir -p $dst:h && mv -n $src $dst done }
It will even create the target directory if it doesn’t exist.
Bonus item: This is more for fun than serious use. An updating clock in your prompt:
_prompt_and_resched() { sched +1 _prompt_and_resched; zle && zle reset-prompt } _prompt_and_resched PS1="%D{%H:%M:%S} $PS1"
As usual, these things and many others are integrated in my .zshrc. Enjoy your Z shell!
NP: Silly—EKG