Skip to content

Instantly share code, notes, and snippets.

@MicahElliott
Last active December 22, 2015 05:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MicahElliott/6427072 to your computer and use it in GitHub Desktop.
Save MicahElliott/6427072 to your computer and use it in GitHub Desktop.
I Get Around (a Zsh overview of the chdir family)

I Get Around

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.

Useful Aliases

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»

Putting CWD into the prompt

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

An aside on stack pushing

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.

cdablevars

To jump to your hottest directories, you can set shortcuts. But first ensure that cdablevars is set.

% setopt cdablevars # option

Setting up cdpath array

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

Tweaking dirs output

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!

Demo: using the dirs shortcuts

% 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.

Ideas for chpwd (a real life example)

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 chpwds 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.)

Resources

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.

Key take-aways

  • 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 than cd.

  • 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 %~.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment