Wow, almost two years have passed since the latest installment of our favorite clickbait zsh tricks series.
When editing long lines in the
zleline editor, sometimes you want to move “by physical line”, that is, to the character in the terminal line below (likegjandgkin vim).We can fake that feature by finding out the terminal width and moving charwise:
_physical_up_line() { zle backward-char -n $COLUMNS } _physical_down_line() { zle forward-char -n $COLUMNS } zle -N physical-up-line _physical_up_line zle -N physical-down-line _physical_down_line bindkey "\e\e[A" physical-up-line bindkey "\e\e[B" physical-down-lineNow, ESC-up and ESC-down will move by physical line.
Sometimes it’s nice to do things in random order. Many tools such as image viewers, music or media players have a “shuffle” mode, but when they don’t, you can help yourself with this small trick:
SHUF='oe|REPLY=${(l:5::0:)RANDOM}${(l:5::0:)RANDOM}${(l:5::0:)RANDOM}|'Just append
($SHUF)to any glob, and get the matches shuffled:% touch a b c d % echo *($SHUF) d c a b % echo *($SHUF) c a d bNote that this shuffle is slightly biased, but it should not matter in practice. In doubt, use
shuforsort -Ror something else…Are you getting sick of typing
cd ../../..all the time? Why not typeup 3?up() { local op=print [[ -t 1 ]] && op=cd case "$1" in '') up 1;; -*|+*) $op ~$1;; <->) $op $(printf '../%.0s' {1..$1});; *) local -a seg; seg=(${(s:/:)PWD%/*}) local n=${(j:/:)seg[1,(I)$1*]} if [[ -n $n ]]; then $op /$n else print -u2 up: could not find prefix $1 in $PWD return 1 fi esac }With this helper function, you can do a lot more actually: Say you are in
~/src/zsh/Src/Builtinsand want to go to~/src/zsh. Just sayup zsh. Or even justup z.And as a bonus, if you capture the output of
up, it will print the directory you want, and not change to it. So you can do:mv foo.c $(up zsh)Previous tricks (#6/#7) introduced the dirstack and how to navigate it. But why type
cd -<TAB>and figure out the directory you want to go to when you simply can typecd ~[zsh]and go to the first directory in the dirstack matchingzsh? For this, we define the zsh dynamic directory function:_mydirstack() { local -a lines list for d in $dirstack; do lines+="$(($#lines+1)) -- $d" list+="$#lines" done _wanted -V directory-stack expl 'directory stack' \ compadd "$@" -ld lines -S']/' -Q -a list } zsh_directory_name() { case $1 in c) _mydirstack;; n) case $2 in <0-9>) reply=($dirstack[$2]);; *) reply=($dirstack[(r)*$2*]);; esac;; d) false;; esac }The first function is just the completion, so
cd ~[<TAB>will work as well.Did you ever want to move a file with spaces in the name, and mixed up argument order?
% mv last-will.tex My\ Last\ Will.rtfPressing
ESC-t(transpose-words) between the file names will do the wrong thing by default:% mv My last-will.tex\ Last\ Will.rtfLuckily, we can teach
transpose-wordsto understand shell syntax:autoload -Uz transpose-words-match zstyle ':zle:transpose-words' word-style shell zle -N transpose-words transpose-words-matchVoila:
% mv My\ Last\ Will.rtf last-will.texIf you are an avid Emacs user like me, you’ll find this function useful. It enters the directory the currently active Emacs file resides in:
cde() { cd ${(Q)~$(emacsclient -e '(with-current-buffer (window-buffer (selected-window)) default-directory) ')} }You need the
emacs-serverfunctionality enabled for this to work.I’m working on many different systems and try to keep a portable
.zshrcbetween those. One problem used to be setting$PATHportably, because there is quite some difference among systems. I now let zsh figure out what belongs to$PATH:export PATH path=( ~/bin ~/.gem/ruby/*/bin(Nn[-1]) ~/.opam/current/bin ~/.cabal/bin ~/.go/bin /usr/local/bin /usr/local/sbin /usr/bin /usr/sbin /sbin /bin /usr/games /usr/games/bin ) path=( ${(u)^path:A}(N-/) )The last line will normalize all paths, and remove duplicates and nonexisting directories. Also, notice how I pick up the latest Ruby version to find the Gem bin dir by sorting them numerically.
One of the hardest things is to set the xterm title “correctly”, because most people do it wrong in some way, and then it will break when you have literal tabs or percent signs or tildes in your command line. Here is what I currently use:
case "$TERM" in xterm*|rxvt*) precmd() { print -Pn "\e]0;%m: %~\a" } preexec() { print -n "\e]0;$HOST: ${(q)1//(#m)[$'\000-\037\177-']/${(q)MATCH}}\a" } esacFor a cheap, but secure password generator, you can use this:
zpass() { LC_ALL=C tr -dc '0-9A-Za-z_@#%*,.:?!~' < /dev/urandom | head -c${1:-10} echo }Sometimes it’s interesting to find a file residing in some directory “above” (e.g.
Makefile,.gitand similar). We can glob these by repeating../using the#-operator (You haveEXTENDED_GLOBenabled, right?). This will result in all matches, so let’s first sort them by directory depth:% pwd /home/chris/src/zsh/Src/Builtins % print -l (../)#Makefile.in(Od) ../Makefile.in ../../Makefile.inNow we can pick the first one, and also make the file name absolute:
% print (../)#Makefile.in(Od[1]:A) /home/chris/src/zsh/Makefile.inI knew the
#-operator, but it never occurred to me to use it this way before.Until next time!
NP: Pierced Arrows—On Our Way