For quite some time—as my research will show, March 2016—, I have
been annoyed by a directory named --help showing up in my home
directory (and sometimes in others).
It’s just an empty, innocent directory with a weird name.
Removed by a simple run of rmdir ./--help.
Yet, it would turn up again.
Sometimes soon, sometimes it took a few weeks.
I had no idea where it came from.
Obviously, a simple mkdir --help just print’s the mkdir help:
Usage: mkdir [OPTION]... DIRECTORY...
Create the DIRECTORY(ies), if they do not already exist.
...
I thought, maybe some program was called once with --help but
didn’t understand it, yet stored it somewhere and would create it when
run again. I suspected many things, especially applications I don’t use that
often, like Gimp or Transmission. I grepped all dotfiles in $HOME
for --help. To no avail.
I decided I had to track this down.
My first thought was to use
fatrace,
which logs all file accesses. But fatrace doesn’t register
directory creation (the fanotify API does, I think).
I didn’t know what to do, and left the problem alone for some time.
The --help directory kept reappearing.
Some time later, I read a book on eBPF, and decided I can log all mkdir(2) calls with it, globally. (I also think I tried this with SystemTap before, but I’m not sure anymore.)
I wrote a bpftrace script called mkdirsnoop, a variant of opensnoop.
It would print all mkdir(2) calls and what program ran it, system wide.
I ran it a lot, but not consistently.
Yesterday, it finally triggered! I tweeted:
I’m really flipping the table.
For months, I’ve been trying to figure out what occasionally creates “–help” folders in my $HOME. I have no idea why it happens.
I wrote a bpftrace script to find the culprit.
Today it triggered.
It says, it’s mkdir(1).
(╯°□しろいしかく°)╯( ┻━┻
If it was mkdir(1), it must have been created by a script. Gimp and Transmission were acquitted of charge.
Julien Kirch proposed to put a different mkdir into my $PATH,
which I think was a smart idea. I wrote this:
#!/bin/sh
printf '%s\n' "mkdir called from $$ at $(date)" >> /tmp/mkdirlog
pstree -sap $$ >> /tmp/mkdirlog
exec /usr/bin/mkdir "$@"
Very quickly, the script triggered, and I saw:
runit,1
`-sh,30996 -c urxvt
`-urxvt,30997
`-zsh,30998
`-zsh,31055
`-mkdir,31058 /home/leah/bin/mkdir --help
`-pstree,31060 -sap 31058
So it was actually zsh calling mkdir --help.
(Or a zsh script.)
But there was no directory --help.
Because calling mkdir --help does not create a directory,
instead printing the help.
Jannis Harder told me that the zsh completion for mkdir would run
mkdir --help (to see if it’s the GNU variant), which I could verify.
But it didn’t explain the --help directory.
However, I had something to track down now.
Using my extrace, I would
log all runs of mkdir with a --help argument, and at some point
I found:
mkdir -p -- --help
Mysterious. I grepped my dotfiles again and found the unsuspicious
function mkcd:
# mkcd -- mkdir and cd at once
mkcd() { mkdir -p -- "1ドル" && cd -- "1ドル" }
This is a helper function I use a lot. But I don’t call it with
--help of course, and then wonder for 4 years why I do that.
Well I don’t. But zsh does, because I also had:
compdef mkcd=mkdir
This was a quick shortcut to complete mkdir’s arguments (only
directories) also for mkcd. I added it in 2013. Even before I added
the -p flag in 2016.
Quickly, I verified that mkcd <TAB> would create a --help
directory. But only if the completion wasn’t loaded already,
e.g. when I didn’t run mkdir <TAB> before. This explains why
the directory only appeared sporadically.
I quickly rewrote the completion:
_mkcd() { _path_files -/ }
compdef _mkcd mkcd
And another mystery of my setup has been solved. Case closed.
NP: Pearl Jam—Take The Long Way
I’ve been using below shell prompt since 2013 and only slightly tweaked it over time. The most significant change was probably displaying the Git branch.
The basic idea of my prompt is to not show redundant or obvious information. This allows the prompt to be short, yet useful.
By default, the prompt displays the hostname, shortened directory, and
a % to signify a zsh.
The hostname is bold to make it stand out when you are scrolling,
and the sigil is colored to mark the beginning of the command.
It looks like this:
juno ~% ./mycommand -x
Long directory names are truncated in the middle:
juno /tmp/dirwithare...gname%
In rare cases, only showing two levels of hierarchy may be confusing,
so you can set $NDIRS to something higher, e.g. 4:
juno deeply/nested/dir/structure%
When the previous command failed, the prompt also displays the exit status of the previous command:
juno 42? ~%
When there are background jobs running, the prompt shows how many there are:
juno 1& ~%
Note how the status and job display use the associated ASCII symbols.
When we are in a Git repository, the current branch is displayed inline as part of the base directory (when possible), or as a prefix, together with the repo name. By design, in the most common cases this keeps the prompt very short:
juno prj/rack@master%
juno rack@master/doc%
juno rack@master doc/Rack%
When the prompt detects a SSH session, the prompt sigil is doubled, so we are a bit more careful there:
hecate prj/lr%%
When the shell runs as root, the sigil is red (I don’t usually run zsh as root):
juno /etc#
That’s it, essentially.
Apart from the Git integration, it’s really straight-forward.
Not visible above is
trick 4 to
simplify pasting of old lines,
and how it updates the title of terminal emulators to
hostname: dir respectively hostname: current-command
(which needs quite complicated quoting).
The whole thing is defined in the PROMPT section of
my .zshrc.
NP: Light Bearer—Aggressor & Usurper
A nice feature I’ve become used to in the last year is a so-called “smart directory changer” that keeps track of the directories you change into, and then lets you jump to popular ones quickly, using fragments of the path to find the right location.
There is quite some prior art in this, such as autojump, fasd or z, but I could not resist building my own implementation of it, optimized for zsh.
As far as I can see, my zz directory changer is the only one with a
“pay-as-you-go” performance impact, i.e., not every directory
change is slowed down, but only every use of the smart matching functonality.
The idea is pretty easy: we add a chpwd hook to zsh to keep track of
directory changes, and log for each change a line looking like "0
$epochtime 1 $path" into a file ~/.zz.
This is an operation with effectively constant cost on a Unix system.
chpwd_zz() {
print -P '0\t%D{%s}\t1\t%~' >>~/.zz
}
chpwd_functions=( ${(kM)functions:#chpwd?*} )
The actual jumping function is called zz:
zz() {
How does the matching work? It’s an adaption of the z algorithm:
The lines of ~/.zz are tallied by directory and last-used time stamp,
so for example the lines
0 1483225200 1 ~/src
0 1483225201 1 ~/tmp
0 1483225202 1 ~/src
0 1483225203 1 ~/tmp
0 1483225204 1 ~/src
would turn into
6 1483225204 3 ~/src
4 1483225203 2 ~/tmp
Also, the initial number, the effective score of the directory, is computed: We take the relative age of the directory (that is, seconds since we went there), and boost or dampen the results: the frequency is multiplied by 4 for directories not older than 1 hour, doubled for directories we went into today, halved for directories we went into this week, and divided by 4 else.
awk -v ${(%):-now=%D{%s}} <~/.zz '
function r(t,f) {
age = now - t
return (age<3600) ? f*4 : (age<86400) ? f*2 : (age<604800) ? f/2 : f/4
}
{ f[4ドル]+=3ドル; if (2ドル>l[4ドル]) l[4ドル]=2ドル }
END { for(i in f) printf("%d\t%d\t%d\t%s\n",r(l[i],f[i]),l[i],f[i],i) }' |
By design, this tallied file can be appended again with new lines
originating from chpwd, and recomputed whenever needed.
The output of this tally is then sorted by age, truncated to 9000 lines,
then sorted by score.
(My ~/.zz is only 350 lines, however.)
sort -k2 -n -r | sed 9000q | sort -n -r -o ~/.zz
With this precomputed tally (which is generated in linear time), finding the best match is easy. It is the first string that matches all arguments:
if (( $# )); then
local p=$(awk 'NR != FNR { exit } # exit after first file argument
{ for (i = 3; i < ARGC; i++) if (4ドル !~ ARGV[i]) next
print 4ドル; exit }' ~/.zz ~/.zz "$@")
If nothing was found, we bail with exit code 1. If zz is used
interactively, it changes into the best match, else the best match is
just printed. This allows using things like cp foo.mkv $(zz mov).
[[ $p ]] || return 1
local op=print
[[ -t 1 ]] && op=cd
if [[ -d ${~p} ]]; then
$op ${~p}
else
If we found a directory that doesn’t exist anymore, we clean up the
~/.zz file, and try it all over.
# clean nonexisting paths and retry
while read -r line; do
[[ -d ${~${line#*$'\t'*$'\t'*$'\t'}} ]] && print -r $line
done <~/.zz | sort -n -r -o ~/.zz
zz "$@"
fi
With no arguments, zz simply prints the top ten directories.
else
sed 10q ~/.zz
fi
}
I actually shortcut zz to z and add a leading space to not store
z calls into history:
alias z=' zz'
The full code (possibly updated) can be found as usual in my .zshrc.
I use lots of shell hacks, but zz definitely is among my most
successful ones.
NP: Leonard Cohen—Leaving The Table
Recently I got around to configuring less, and I collected these few tricks:
Sometimes I look at lists with less, and then do things step-by-step,
keeping the current action at the top of the page. This works nicely
until you end up at the last page of the file, and then can’t scroll
down. You lose track of where you are at and get confused.
It would be much nicer scrolling down, and filling up the buffer
with ~ after the end of file, just as if you had searched in the pager.
Actually, with ESC-SPC, you can move a full page down, filling up
the buffer with ~. Toying around a bit, you’ll find out that you
can override the “page length” with a prefix, i.e. 1 ESC-SPC will move
down one line only!
However, this is still inconvenient to type all the time, thus let’s
define a keybinding. For this, create a file ~/.lesskey where we will
put the key definitions. This file then will be compiled using lesskey(1)
and generate a binary configuration file ~/.less.
(I guess you can be lucky that m4 is not involved in this mess…)
One problem is actually binding the key. You can easily bind
the cursor down key (\kd) to forw-screen-force, but how do you pass 1?
The canonical hack is to use the noaction action, which will behave just
like you’ve typed the keys after it. Thus, we write:
#command
\kd noaction 1\e40円
j noaction 1\e40円
(By the way, that #command comment is important to tell lesskey
you are defining key commands.)
Finally, scrolling bliss!
Actually, scrap that.
The badly underdocumented key J (and K) will scroll how I
want, but you only read about that in the example inside lesskey(1).
Therefore, we can just do:
#command
\kd forw-line-force
j forw-line-force
These keybindings are there since at least 1997 and I’ve never found them before…
While we are redefining keys, I’ve always found it a bit clumsy to
read multiple files, having to type :n and :p. Using [ and
] is much more convenient (at least on a US keyboard), and by default
these keys do things of questionable utility.
#command
[ prev-file
] next-file
Did you ever wish to give feedback from less?
Like have a script output some info, and you decide how to go on?
Since less always exits with status 0 usually, this I thought
this was tricky to do, but the quit action actually can return
an arbitrary exit code, encoded as a character.
I bound Q and :cq (like in vim) to exit with status 1:
#command
Q quit 1円
:cq quit 1円
Now you can do stuff like look at all files and have them deleted
when you press Q instead of q to exit:
for f in *; do less $f || rm $f; done
I use less a lot to look at patches, git log output, and
ocassionally mailboxes. The D command as defined below will
move to the next line starting with diff or commit or From␣.
#command
D noaction j/\^diff|commit|From \n\eu
It will also “type” ESC-u to hide the highlighting.
Now I can simply press D to jump to the next chunk of interest.
To return to where you started from after a search or going to the
end of file, type ''. Typing '' again will go back, so this is
also nice to toggle between two search results.
Back in the old days of X11R2(?) there was a tool called xless,
which was exactly that: a pager like less that ran in its own X11
window. It’s quite useful. We can recreate this by combining a
X11 terminal emulator and plain less with a small zsh snippet:
xless() {
{
exec {stdin}<&0 {stderr}>&2
exec urxvt -e sh -c "less ${(j: :)${(qq)@}} </dev/fd/$stdin 2>/dev/fd/$stderr"
} &!
}
Watch the trick how we pass the stdin/stderr file descriptors and the file arguments!
Now you can just run command-spitting-out-loads | xless and the
output will be shown in a new terminal and not lock your shell.
NP: Feine Sahne Fischfilet—Dreieinhalb Meter Lichtgestalt
Wow, almost two years have passed since the latest installment of our favorite clickbait zsh tricks series.
When editing long lines in the zle line editor, sometimes you
want to move “by physical line”, that is, to the character in the
terminal line below (like gj and gk in 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-line
Now, 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 b
Note that this shuffle is slightly biased, but it should not
matter in practice. In doubt, use shuf or sort -R or something else…
Are you getting sick of typing cd ../../.. all the time? Why not
type up 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/Builtins and want to go
to ~/src/zsh. Just say up zsh. Or even just up 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 type cd ~[zsh] and go to the first directory in the dirstack
matching zsh? 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.rtf
Pressing ESC-t (transpose-words) between the file names will do
the wrong thing by default:
% mv My last-will.tex\ Last\ Will.rtf
Luckily, we can teach transpose-words to understand shell syntax:
autoload -Uz transpose-words-match
zstyle ':zle:transpose-words' word-style shell
zle -N transpose-words transpose-words-match
Voila:
% mv My\ Last\ Will.rtf last-will.tex
If 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-server functionality enabled for this to work.
I’m working on many different systems and try to keep a portable
.zshrc between those. One problem used to be setting $PATH
portably, 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" }
esac
For 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, .git and similar). We can
glob these by repeating ../ using the #-operator (You have
EXTENDED_GLOB enabled, 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.in
Now we can pick the first one, and also make the file name absolute:
% print (../)#Makefile.in(Od[1]:A)
/home/chris/src/zsh/Makefile.in
I knew the #-operator, but it never occurred to me to use it this
way before.
Until next time!
NP: Pierced Arrows—On Our Way
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
Since its release I’ve been a fan of Git. (I still can remember downloading the initial version.) The thing I like most is that it can be extended and customized in an unixy way. Over time, I have collected some scripts and tricks that I would like to present to a wider audience. Git information online abounds (I especially recommend Mark J. Dominus in-depth posts on Git), thus I will only show stuff I haven’t seen elsewhere.
Let’s start with a simple alias which you can simply add to your .gitconfig:
[alias]
news = log -p HEAD@{1}..HEAD@{0}
I am tracking quite a lot of open source projects by cloning them into
~/src and running git pull on them occasionally. Next, I run
git news and see only the commits (with diff) that have arrived
since the last pull.
Of course it is a very simplistic alias and it probably won’t do what
you want if you actually change the HEAD yourself—e.g. by committing.
(A more robust version could, for example, parse the output of git
reflog and search for the last pull.) On the other hand, as it is, it
also can be useful for showing what came in with a merge. I also use
it for repositories where I git cvsimport into, with the same
benefits.
Admittedly, I’m a fan of dirty working trees, which is why—when I
don’t use magit or finely-grained git add -p/git commit -p
already—I commit whole files at once like git commit foo.c bar.c.
One thing that has always annoyed me is that I cannot git commit
files unknown to Git, enforcing an explicit git add step only for
these new files! One day I took the plunge and wrote
git-comma (a portmanteau of commit and add) which gives
its best to behave exactly like git commit except for adding the
yet-unknown files beforehand. This was a bit more tricky than I
expected because I wanted it to work correctly even in the face of
partially staged files, thus a stupid git add on all arguments would
not work (also, you only want to add explicitly named files, not whole
directories and so on). Finally, git comma tries to clean up
properly if you decide to abort the commit, unstaging the files again.
(IMO, this should be a flag or configuration option for git commit.)
A newer script, but a very useful one, is git attic, whose namesake
perhaps gives you a shiver down the spine, being reminded of this CVS
quirk.
Yet, CVS’ manner with deleted files—moving them into a folder called
Attic—had one benefit which cannot be denied: it was easy to see
what had been removed and to access the contents again.
Of course, Git has no problem with file removal, but having a look at the old contents can be laborious.
Thus I wrote git-attic, which presents you a nice list of
files together with their deletion date:
% git attic
2012年08月14日 441e782^:Etc/ChangeLog-5.0
2012年05月31日 0793393^:Completion/Unix/Command/_systemctl
2012年01月31日 6a364de^:Test/Y04compgen.ztst
2012年01月31日 6a364de^:Test/compgentest
2011年08月18日 f0eaa57^:Completion/Zsh/Command/_schedtool
...
The output is designed to be copy’n’pasted: Pass the second field to
git show to display the file contents, or just select the hash
without ^ to see the commit where removal happened.
(By default, I don’t detect renames, since I want to see which paths
don’t exist anymore. If you are looking for “lost” content, feel free
to pass -M to the script to detect renames and only show truly
deleted files.)
As an avid zsh user for years, I have been using a
simple but powerful shell prompt which looks like hecate src/zsh%
for years (since 2010年02月11日 actually, thanks to homegit, see below.)
and ridiculed experiments to make the zsh prompt a kitchen
sink. However, my Git usage grew and I started occasionally
mixing up branches.
Thus I decided to grin and bear it and wondered how to make a minimalist nevertheless useful Git-enhanced prompt. One feature of my prompt was that it only shows the last few segments of the current working directory (usually 2, which is enough for me unless I need to work in some javaesque file labyrinth). One day I decided to integrate the current Git branch into these path segments. Now, my prompt looks like this:
hecate src/zsh@master% cd Doc
… and it actually sticks to the repository root:
hecate zsh@master/Doc% cd Zsh
When the level gets too deep, the branch and repository moves to the front:
hecate zsh@master Doc/Zsh%
The depth is still configurable:
hecate zsh@master Doc/Zsh% NDIRS=4
hecate src/zsh@master/Doc/Zsh%
I’ve quite come to like this presentation. Additionally, it also works with detached heads (useful when rebasing):
hecate src/zsh@master/Doc/Zsh% git checkout HEAD~42
...
hecate src/zsh@master~42/Doc/Zsh%
For free, you get some feedback when bisecting:
hecate ~/src/zsh@master% git bisect bad
hecate ~/src/zsh@bisect/bad% git bisect good HEAD~42
hecate ~/src/zsh@bisect/bad~21% git bisect good
hecate ~/src/zsh@bisect/bad~5% git bisect reset
hecate ~/src/zsh@master%
This is the code in all its glory:
# gitpwd - print %~, limited to $NDIR segments, with inline git branch
NDIRS=2
gitpwd() {
local -a segs splitprefix; local prefix gitbranch
segs=("${(Oas:/:)${(D)PWD}}")
if gitprefix=$(git rev-parse --show-prefix 2>/dev/null); then
splitprefix=("${(s:/:)gitprefix}")
branch=$(git name-rev --name-only HEAD 2>/dev/null)
if (( $#splitprefix > NDIRS )); then
print -n "${segs[$#splitprefix]}@$branch "
else
segs[$#splitprefix]+=@$branch
fi
fi
print "${(j:/:)${(@Oa)segs[1,NDIRS]}}"
}
Perhaps it turned out to be a bit more challenging than expected. ;) Integration into the prompt is trivial, however:
function cnprompt6 {
case "$TERM" in
xterm*|rxvt*)
precmd() { print -Pn "\e]0;%m: %~\a" }
preexec() { printf "\e]0;$HOST: %s\a" 1ドル };;
esac
setopt PROMPT_SUBST
PS1='%B%m%(?.. %??)%(1j. %j&.)%b $(gitpwd)%B%(!.%F{red}.%F{yellow})%#${SSH_CLIENT:+%#} %b'
RPROMPT=''
}
cnprompt6
For the last five years I have used Git to manage my dotfiles and I use the repository on a plethora of machines.
I found the following zsh alias to be the simplest and best method
to use Git for this purpose:
alias homegit="GIT_DIR=~/prj/dotfiles/.git GIT_WORK_TREE=~ git"
Why not a function? Because an alias will make zsh autocomplete
homegit just like it completes git already, without any additional
work.
Why not a ~/.git? I decided against it because I didn’t want to
accidentally commit stuff from any subdirectory and feared a git
clean could wipe my sweet home directory.
The homegit approach works very well for me and I have not felt a
need for more complex solutions which symlink dotfiles or copy them
around.
Note that the git-* scripts presented here can be called
transparently from homegit as well, e.g. with homegit attic. And
since $GIT_DIR is set in the environment, the scripts can just call
git and will just work correctly!
411 commits as of now tell me I perhaps should scale back customizing stuff all the time, but it can be very helpful indeed to see how things changed over time. Also, tracking changes other programs make to your files (and being able to revert them) is totally worth it.
One of the newest additions to my Git zoo is git trail, a tool I
wanted for years, really. With many branches, it’s easy to get
confused about what branched off where and what actually is part of
this topic branch and whether this topic branch has been merged but
then forgotten or…
Perhaps you feel my pain. Perhaps you tried git show-branch once to
get an overview of such a mess, but I feel it’s easier to see
stereographic projections of a T-Rex in its output than the state of
your branches.
Thus I wrote git-trail, which shows how to reach commits in
the current branch from other branches. Since we don’t have enough
local branches to make it interesting, lets show remote branches too
(-r):
hecate tmp/rack@master% git trail -r
2013年01月04日 7e1f081 master
2013年01月04日 7e1f081 remotes/origin/HEAD
2013年01月04日 1e75faa remotes/origin/hijack~2
2013年01月04日 1e75faa remotes/origin/master~1
2012年11月03日 1824547 remotes/origin/unstandard_uri_escape~1
2012年03月18日 7d7977f remotes/origin/rack-1.4~77
2011年05月22日 a50dda5 remotes/origin/rack-1.3~99
2010年06月15日 dc6b54e remotes/origin/rack-1.2~38
2010年01月03日 e6ebd83 remotes/origin/rack-1.1~23
2009年04月25日 d221938 remotes/origin/rack-1.0~24
2009年01月05日 7fed4c7 remotes/origin/rack-0.9~15
2008年08月09日 e9f9f27 remotes/origin/rack-0.4~6
What you see is the first common commit between every branch and the
current branch, together with the commit date. If the branch is
listed without suffixes, it is completely included. Else, you
effectively see how the branch diverges. For example, in rack-1.4,
there have been 77 patches since branching from master. The feature
branch hijack consists of two commits. Lets look at the view from
that feature branch:
hecate tmp/rack@master% git trail -r origin/hijack
2013年01月04日 8a311fb remotes/origin/hijack
2013年01月04日 1e75faa master~1
2013年01月04日 1e75faa remotes/origin/HEAD~1
2012年11月03日 1824547 remotes/origin/unstandard_uri_escape~1
2012年03月18日 7d7977f remotes/origin/rack-1.4~77
2011年05月22日 a50dda5 remotes/origin/rack-1.3~99
2010年06月15日 dc6b54e remotes/origin/rack-1.2~38
2010年01月03日 e6ebd83 remotes/origin/rack-1.1~23
2009年04月25日 d221938 remotes/origin/rack-1.0~24
2009年01月05日 7fed4c7 remotes/origin/rack-0.9~15
2008年08月09日 e9f9f27 remotes/origin/rack-0.4~6
We see that there have been commits to master since hijack was
branched, and we should perhaps rebase hijack if we wanted to submit
it.
Let’s say we simply merged it into master:
hecate tmp/rack@master% git merge origin/hijack
...
hecate tmp/rack@master% git trail -r
2013年01月06日 68de794 master
2013年01月04日 8a311fb remotes/origin/hijack
2013年01月04日 7e1f081 remotes/origin/HEAD
2012年11月03日 1824547 remotes/origin/unstandard_uri_escape~1
2012年03月18日 7d7977f remotes/origin/rack-1.4~77
...
Now hijack appears undecorated: it is completely contained in the
current branch history.
Let’s say we work on the other feature branch next, unstandard_uri_escape:
hecate tmp/rack@master% git checkout unstandard_uri_escape
hecate tmp/rack@unstandard_uri_escape% git trail
2012年11月03日 decaa23 unstandard_uri_escape
2012年11月03日 1824547 master~10^2~1
We can now rebase it to make it a proper child of master:
hecate tmp/rack@unstandard_uri_escape% git rebase master
hecate tmp/rack@unstandard_uri_escape% git trail
2013年01月06日 92b40fa unstandard_uri_escape
2013年01月06日 c30da33 master
And then master can be fast-forwarded:
hecate tmp/rack@unstandard_uri_escape% git checkout master
hecate tmp/rack@master% git trail
2013年01月06日 c30da33 master
2013年01月06日 c30da33 unstandard_uri_escape~1
hecate tmp/rack@master% git merge unstandard_uri_escape
Updating c30da33..92b40fa
Fast-forward
...
hecate tmp/rack@master% git trail
2013年01月06日 92b40fa master
2013年01月06日 92b40fa unstandard_uri_escape
I hope this exposed how git trail helps me to keep track of dealing
with branches.
The perfect match for git-trail is git-neck, which show commits
from the HEAD until the first branching point… that should explain the name.
So, what is the “neck” of our master branch as above?
hecate tmp/rack@master% git neck -r
92b40fa Add a decoder that supports ECMA unicode uris
c30da33 Merge remote-tracking branch 'origin/hijack'
7e1f081 Merge pull request #480 from udzura/master
3edd1e8 Add a rackup option for one-liner rack app server
6d41179 Extract Builder.new_from_string from Builder.parse_file
Likewise, let’s have a look at that remote feature branch sticking around:
% git neck -r origin/unstandard_uri_escape
decaa23 Add a decoder that supports ECMA unicode uris
It was just a single commit. We can also look at the neck of an old release branch:
hecate tmp/rack@master% git neck -r origin/rack-0.4
92f79ea Make Rack::Lint::InputWrapper delegate size method to underlying IO object.
e33cc65 Update to version 0.4
ab9a95e Fix packaging script
1ccdf73 Update README
1b56583 Document REQUEST_METHOD future changes
f0977a8 Disarm and document Content-Length checking in Rack::Lint for 0.4
And we see the 6 commits that are only in rack-0.4.
If you remember the situation before merging the feature branches:
hecate tmp/r2@master% git trail -r
2013年01月04日 7e1f081 master
2013年01月04日 7e1f081 remotes/origin/HEAD
2013年01月04日 1e75faa remotes/origin/hijack~2
2013年01月04日 1e75faa remotes/origin/master~1
2012年11月03日 1824547 remotes/origin/unstandard_uri_escape~1
Here, the neck is the part until master forked off:
hecate tmp/r2@master% git neck -r
7e1f081 Merge pull request #480 from udzura/master
3edd1e8 Add a rackup option for one-liner rack app server
6d41179 Extract Builder.new_from_string from Builder.parse_file
git neck is most useful if you are working in a feature branch which
no other branch forks off, because then the neck goes until where you
forked it.
At last, another small trick: git diff works between any two files
(or directories), even if you don’t use Git at all to track them. But
you gain some advantages over regular diff, like --word-diff,
--color or --stat without having additional tools beyond Git
installed.
Also, you can use git diff --binary to generate efficient binary
deltas which you can apply again provided you have the unpatched file.
(Possibly you need to edit the patch to make both filenames the same,
so git apply finds everything.)
NP: Sophie Hunger—What it is
It’s been over a year since the last installment of the series.
Sifting through others’ .zshrc, one occasionally finds aliases like:
alias ls=' ls'
alias cd=' cd'
… and so on. The reason for this is simple, but not obvious:
with setopt HIST_IGNORE_SPACE, these commands will be ignored, even
if you don’t start these commands with a space.
We already talked about brace expansion like {1..5} which expands
to 1 2 3 4 5. But did you know you also can do {1..10..2}, which expands
to 1 3 5 7 9 and {005..14..2} which expands to 005 007 009 011 013?
Oh, and {10..1} works as well as {10..1..2}.
Now you can throw seq(1) away!
Using C-r and C-s to search history is well known, but the default search
is a bit limited. Use these lines to enable search by globs,
e.g. gcc*foo.c:
bindkey "^R" history-incremental-pattern-search-backward
bindkey "^S" history-incremental-pattern-search-forward
One nice trick if you often suspend vi by C-z:
foreground-vi() {
fg %vi
}
zle -N foreground-vi
bindkey '^Z' foreground-vi
This will make C-z on the command line resume vi again, so you can toggle between them easily. Even if you typed something already!
zsh has lots of documentation, but finding what you want to know can be difficult. The manpage zshall(1) contains everything, and this function will make it easy to search in:
zman() {
PAGER="less -g -s '+/^ "1ドル"'" man zshall
}
Try zman fc or zman HIST_IGNORE_SPACE! (Use n if the first match
is not what you were looking for.)
Recently, I’ve become an avid user of the directory stack, but not really for its intended usage; instead, I use it together with the next trick. Here’s how you can persist the dirstack across sessions:
DIRSTACKSIZE=9
DIRSTACKFILE=~/.zdirs
if [[ -f $DIRSTACKFILE ]] && [[ $#dirstack -eq 0 ]]; then
dirstack=( ${(f)"$(< $DIRSTACKFILE)"} )
[[ -d $dirstack[1] ]] && cd $dirstack[1] && cd $OLDPWD
fi
chpwd() {
print -l $PWD ${(u)dirstack} >$DIRSTACKFILE
}
First, we limit the dirstack to nine entries, load them from
.zdirs if possible, and then we save them again on every directory
change.
For a long time, I used to have something similar that only saved $OLDPWD,
so I could open a new shell and cd - and be back where I last changed to.
But now I use this, and AUTO_PUSHD, together with the next trick.
Every zsh user knows that you can use dirs to display the dirstack,
and cd -N to go to the N-th element.
But did you know zsh will show the dirstack on cd -TAB?
It’s awesome, and does all the directory jumping I need.
% cd -TAB
1 -- /home/chris/mess/current
2 -- /home/chris/mess/current/mdnsd
3 -- /home/chris/mess/current/mdnsd/libutil
4 -- /home/chris
5 -- /home/chris/src/aewm-1.2.7/clients
6 -- /home/chris/mess/2011/47/fspanel-0.7
7 -- /home/chris/mess/2011/47
8 -- /home/chris/src/mcwm
Which words end with ‘tent’? Of course you can do
grep tent$ /usr/share/dict/words,
but did you know you can do look _tent and
press TAB (_ is where the cursor is)?
This fantastic zsh trick is from Julius Plenz: complete words from tmux pane.
Perhaps you know zmv already, but it can be a bit nasty.
E.g. to rename all *.lis files to *.txt, the manual recommends:
zmv '(*).lis' '1ドル.txt'
However, with the awesome -W mode, you can write this instead:
zmv -W '*.lis' '*.txt'
If you are not sure what happens, use the dry-run mode first (-n).
That concludes this, now hopefully yearly, installment. Perhaps you’ll find even more new more things in my recently cleaned up .zshrc. Enjoy your Z shell!
NP: EMA—Milkman
It’s been almost three years since the last installment, so here is the next dollop of tips:
=(command) expands to a tempfile with the output of command
that is deleted after the line has finished. In effect, the
same as <(command) but allows applications to seek. E.g.:
xpdf =(zcat foo.pdf.gz)
!-history-expansion is nice, but can be confusing if you have a
command line with many ! that should be left alone. Either quote the
! with single quotes or write !" at the beginning of the line
(yes, that " is left unclosed):
% !" echo Hey there! Wow!!
Hey there! Wow!!
An application of modifiers is !:t, which results into the
basename of the last argument. Very useful when working with URLs,
for example. You’ll never have to strip the path manually again:
% wget ftp://ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p330.tar.gz
% tar xzvf !:t
When playing with parameter expansion flags, it often is annoying having to use variables for immediate values:
% foo=bar.c; echo ${foo:a:u}
/HOME/CHRIS/BAR.C
Instead of the ugly solution
% echo ${$(echo bar.c):a:u}
better use this:
% echo ${${:-bar.c}:a:u}
Here, ${:-bar.c} is an instance of the well-known ${FOO:-BAR}
default substition operator.
To run a command several times, use repeat. Useful for benchmarks, e.g.:
% repeat 3 time sleep 1
sleep 1 0.00s user 0.00s system 0% cpu 1.002 total
sleep 1 0.00s user 0.00s system 0% cpu 1.005 total
sleep 1 0.00s user 0.00s system 0% cpu 1.002 total
Use glob modifiers to sort glob expansions. Helpful are: (om) (sort by modification time) or (n) (sort numerically):
% pdfjoin chapter*.pdf(n) -o all.pdf
Another useful glob modifier is P, for example to prefix a flag:
% tar czvf foo.tar.gz * *.tmp(P:--exclude:)
(yes, tar can exclude patterns, but some other tools can’t, and zsh
does patterns better anyway.)
Some ZLE hacks I use. To override default completion in various ways:
# Force file name completion on C-x TAB, Shift-TAB.
zle -C complete-files complete-word _generic
zstyle ':completion:complete-files:*' completer _files
bindkey "^X^I" complete-files
bindkey "^[[Z" complete-files
# Force menu on C-x RET.
zle -C complete-first complete-word _generic
zstyle ':completion:complete-first:*' menu yes
bindkey "^X^M" complete-first
A function to make adding flags or prefixing arguments easier:
# Move to where the arguments belong.
after-first-word() {
zle beginning-of-line
zle forward-word
}
zle -N after-first-word
bindkey "^X1" after-first-word
Complete with words in the history (like Emacs dabbrev):
# Complete in history with M-/, M-,
zstyle ':completion:history-words:*' list no
zstyle ':completion:history-words:*' menu yes
zstyle ':completion:history-words:*' remove-all-dups yes
bindkey "\e/" _history-complete-older
bindkey "\e," _history-complete-newer
Of course, all things are mentioned in the comprehensive manual, or the great User’s Guide to the Z-Shell which I wholeheartedly recommend. But one needs to find them. :)
NP: Aimee Mann—Freeway
Ok, I’m fed up. Writing a good package manager for Ruby is a fight against windmills.
So let’s do the easiest thing that could possibly work. Redefining
Kernel#require is a no-go, for it will lead to the gates of
hell.
Installing multiple projects to the same location is error-prone,
requires non-trivial amounts of code and introduces packaging effort.
Luckily, most packages these days run directly from a checkout or
their released archives (and if you provide neither, you’re doing it
wrong).
Essentially, all you need to make it convenient setting and
manipulating $RUBYLIB, “A colon-separated list of directories that
are added to Ruby’s library load path ($:).” The Z shell
(1,
2) to the
rescue!
Add this to your .zshrc (or .zshenv, if you want it in non-interactive
shells as well):
# unique, exported, tied array of $rubylib to colon-seperated $RUBYLIB
# 08aug2008 +chris+
typeset -T -U -gx -a RUBYLIB rubylib ':'
rubylib-add() { rubylib+=("$@") }
rubylib-del() { for i ("$@") { rubylib[(r)$i]=() } }
rubylib-reset() { rubylib=(); [[ -f ~/.rubylib ]] && source ~/.rubylib }
rubylib-reset
This creates a zsh array rubylib which value reflects $RUBYLIB and
vice versa (zsh does the same for $PATH and $MANPATH, for example),
and defines three functions to add and remove paths as well as reset
the variable.
Also, create a file ~/.rubylib where you set the defaults.
I simply use:
rubylib-add ~/projects/{testspec,bacon,rack}/lib
rubylib-add ~/src/{camping,markaby}/lib
Remember, you can use the full power of zsh to set this:
rubylib-add ~/src/rubystuff/*/(lib|ext)(/)
You need to use (x|y) instead of {x,y} here to only expand to
existing files. The final (/) ensures these files really are directories.
Reload your .zshrc, and voila, your packages are accessible from every
Ruby script. Now, if a project has different requirements, just
create a script there to adjust $RUBYLIB. Or use vared to
interactively change the load path.
[Thanks to et for improving rubylib-del.]
NP: Curve—Dirty High
…yet, that is. Or if you do, you read the man page pretty well. :-)
zsh has gazillions of features, but I think these are pretty useful
for daily use:
su -c or ssh).<(command) returns the filename (in /dev/fd if supported or as a FIFO)
of the pipe given by command for reading. (For example,
use diff <(ruby foo.rb) <(ruby-1.9 foo.rb) to compare two program outputs).cd old new substitutes old with new once in the pwd and
chdirs there.!$ expands to the previous history line’s last argument, !^
expands to the first argument, !:n to the n-th argument.=foo expands to the full path of foo in the PATH (like which foo).for src in *.c do ... done can be abbreviated to for src (*.c) { ... }
(which is actually memorizable). You can even drop the curly braces
if you don’t have ; in the command.<42-69> globs numbers between 42 and 69. Drop the number(s) to make
it open-valued. {42..69} expands to the numbers between 42 and 69.*** expands recursively like **, but follows symbolic links.More tricks:
Happy hacking.
NP: Shriekback—Mistah Linn He Dead
With the help of the nice folks in #ruby-de, I finally switched to
the Z shell after using GNU
Bash for over seven years (with a
rather short tcsh intermezzo,
admittedly).
I’ve wanted to do the switch for some time, but I never managed to
make it familiar enough for daily use (the default setup is
unusable). Well, I now got a reasonable .zshrc together, and want
like to share some parts of it.
Completion is the most awesome feature of zsh, although the recent Bash versions are steadily coming closer. Let’s enable the completion:
zmodload zsh/complist
autoload compinit && compinit
I don’t want it to think // means anything special, though:
zstyle ':completion:*' squeeze-slashes true
Add colors like in ls(1) for the file name completion:
eval `gdircolors`
zstyle ':completion:*' list-colors ''
zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
And then, make the completion feel as Bash-like as possible (show all completions after two TABS, don’t cycle, put the prompt always below the completions):
setopt BASH_AUTO_LIST
setopt NO_AUTO_MENU
setopt NO_ALWAYS_LAST_PROMPT
History management, this is essentially how I configured it for Bash too:
setopt HIST_IGNORE_DUPS
setopt HIST_IGNORE_SPACE
setopt APPEND_HISTORY
setopt EXTENDED_HISTORY
HISTFILE=~/.zsh_history
SAVEHIST=1000
HISTSIZE=1000
The prompt, I used a rather bloated prompt with Bash; this is a stripped-down version with everything removed I don’t need. (And the monochrome version.)
PROMPT="%(?..[%?] )%(1L.%L.)#%h<%l>%m:%(4~,.../,)%3~%(!.#.$) "
How it looks, for example:
[1] 2#276<p5>lilith:.../rack/lib/rack$
This means, the last command exited with status 1, it is a
second-level subshell, line 276 of history (for !), running on
ttyp5 on my trusty iBook lilith, and I’m a normal user ($) in
the rack/lib/rack directory on my disk (I know where that is).
I don’t use a right-hand-side prompt at the moment because I think it’s confusing, especially if your terminal isn’t very wide.
Terminal title, using a precmd. Host and path are enough for my purposes.
precmd () {print -Pn "\e]0;%n@%m: %d\a"}
Z line editor, zsh’s version of readline has a very good Emacs
keybinding support which we’ll enable and configure it to regard /
as a word seperator too (highly useful for editing paths):
WORDCHARS=${WORDCHARS//[&=\/;!#%{]}
bindkey -e
Mess, finally, mess dir support for zsh:
function mess {
DIR=`~/bin/mess.rb "$@"`
[[ $? -eq 0 ]] && cd "$DIR"
}
That’s essentially it; as you see, nothing really special but it’s a non-trivial configuration I’m very comfortable with so far.
Now, what benefits does zsh give me?
** alone is worth the switch).|& for 2>&1, which I always get wrong).for x (1 2 3); echo $).The only thing so far I couldn’t find out was how to disable file name escaping in the completion list, but that’s a minor aesthetic feature. Feel free to tell me if you know how that can be fixed.
Addition from January 19, 2007: zsh supports UTF8 line editing if the
locale is right. For English messages, therefore set
LANG=en_US.utf8 as LANG=C will not work.
NP: Guns N’ Roses—There Was A Time