We all know how to use cd
(aka chdir
). It’s probably the first command you
ever learned. Or maybe that was ls
. Anyway, when paired with some cousins
(pushd
, popd
, dirs
, cdpath
, chpwd
) it’s a lot more capable than you
may have realized.
I find it tedious to type out all the family (“cd-fam” henceforth) of navigation commands in full length, so let’s start by slimming down – we’re going to use these a lot!
% alias pu=pushd
% alias po=popd
% alias d=dirs
All you ever really need to commit to memory are: cd
, pu
, po
, d
(and
the ~
shortcuts).
A beautiful capability Zsh sports is being able to treat aliases as
first-class commands. So with an alias like lo
it will still complete as if
it were the real thing:
% alias lo='git log' # put this and many others in your .zsh rc file(s)
% lo -«tab»
--abbrev -- set minimum SHA1 display-length
--abbrev-commit
…
--walk-reflogs -g -- walk reflog entries from most recent to oldest
-z -- use NUL termination on output
So you can do this with our cd-fam too:
% pu «tab»
It’s really useful to always know where you are in your filesystem. Rather
than constantly typing pwd
, you can print your PWD
in your prompt. Try
this for a nice bold blue prompt:
% export PROMPT='%F{blue}%B%~%f %%%b%f ' # note that %~ is the pwd
For a time, I found the pu
behavior to be counter-intuitive. We turn
to it often instead of cd
when there is something important to be pushed
(saved). My intuition felt that I was marking the new specified dir as the
save target, but this isn’t the case. Instead, you must train yourself to say
“mark the place I’m sitting as special, and go to the new place which isn’t
being put on the stack.” I suggest you just get used to this if it throws you
at first.
To jump to your hottest directories, you can set shortcuts. But first ensure
that cdablevars
is set.
% setopt cdablevars # option
Now tell Zsh where you’re most likely to be going. You’ll want to set cdpath
in your startup files.
% alias p='print -n'
% p $cdpath # array
This is going to enable you to do things like:
% cdpath+=~/config/shell/zsh # add a dir
# Or add a few
% cdpath+=(~/proj ~/archive/src ~/tmp/gists ~/exp/js)
% p $cdpath[-1]
/home/mde/exp/js
% pwd
/home/mde
% pu fun«tab»
% pu functions # magic!
% pwd
~/config/shell/zsh/functions
% cd mi/a/c«tab» # even complete partial dirs!
You can also set temporary hot spots (named directories), like sg
in this
case. Note that cd-fam tab completion works awesomely, as always.
% sg=~/proj/really/deep/place
% cd s«tab»
--- local directory
scripting-conventions/ split-and-join/
--- directory in cdpath
shell/ sysinfo/
--- user
saned sshd stunnel4 sys
speech-dispatcher statd sync syslog
--- named directory
sg
I personally don’t like what dirs
tells me by default, like constantly
printing the $dirstack
(the key array used by cd-fam) every time I change,
or adding duplicates. Options worth considering:
% setopt pushdignoredups pushdsilent chaselinks pathdirs
You can use the tabs
command to control its width (your default is probably
8, which is a little hard to eyeball-align).
% tabs 4
% d # much better now!
% cd $TMPDIR
% pu ~/proj
% pu foo/bar
% d
% pu ~3
% po
% d
Note the use of ~3
. It’s a reference to the third dir in $dirstack
. cd
and d
support them (~1
and on), and I find myself using them constantly.
Read all about “Hooks” if you’re curious.
% man zshmisc
/hook
% some_famous_quote="The only limit to your impact is your imagination and commitment."
% alias s='sed -r'
% s 's/impact/directory navigation/' <<<$some_famous_quote
This is true of chpwd
too!
For several years, back in my pre-Zsh days, I relied on a little tool called
“Enhanced CD.” It was just a do-everything version of cd-fam. Now with Zsh,
ecd
is wholly replaced by what I’m showing you in this tutorial. Its essence
was to make use of .enter
and .exit
files that lie around in directories.
Today some tools (e.g. RVM, rbenv) use a similar strategy for source
-ing
little RC (run-commands) files. The idea is that you can, for example, enable
some gems, set a umask
, turn on some options, set environment variables,
check your VCS status, whatever — as you enter and leave any directory. You
want to make the setting as idempotent as possible by having an equivalent
.exit
file. This might have first been popularized in an early edition of
the Power Tools book.
So this is what chpwd
is built for. Let’s look at an example of adding
behavior to it via hooks.
Before you accidentally disable something important, check to see if you’ve
got any chpwd
s set.
% p $chpwd_functions
__rvm_project_rvmrc
__rvm_after_c
Okay, I don’t want to mess with these, so I’ll just add a silly function to the array.
% sayhi() print 'in a chpwd array func'
% chpwd_functions+=sayhi
% cd /tmp
hi
Now this is kinda cool — I can also use the chpwd
function in tandem. In other
words, the chpwd
function doesn’t interfere with the chpwd_functions
array.
% chpwd() print 'in chpwd func'
% cd
in THE chpwd func
hi
Now let’s do something useful.
% cd $TMPDIR
% >.enter
print 'in .enter'
^D
% enterrc() { [[ -f .enter ]] && source .enter }
cd && cd $TMPDIR
Notice that chpwd
doesn’t actually run until after we’ve already made it to
the new directory. To access the directory you were in previously you can use
the OLDPWD
param.
p $OLDPWD
The previous example is not exactly robust, but you can go a long way with
chpwd
, so play around.
(We can break a path down into its hierarchical pieces with the “split” flag/operator (zshexpn(1): Parameter Expansion). …cutting short since this is getting long.)
If you’re interested in an even more sophisticated system for managing your movement, you could look at cdr or fasd, and I’m sure many others, but it’s nice to just rely directly on Zsh when possible.
-
cd
,pu
,d
are quite featureful and should be aliased and used a lot. -
They support robust tab completion and
~1
shortcuts. -
Get used to using
pu
more often thancd
. -
cdpath
provides tab-completable shortcut dirs that you can set permanently or as-needed. -
Tie into
chpwd
hooks to get ultimate control of cd-fam. -
Put PWD into your prompt with
%~
.