My key requirement for a file system layout is:
Obvious right?
There are a few implications of that basic rule.
- It should be instinctively obvious where any file in the system is (after an initial acclimatisation period).
- There should be minimal typing required to get to any directory
- The system should scale without additional effort.
- Directories used less frequently can be (slightly) harder to get to.
Screencast
The Tools
My shell of choice is zsh, you may prefer bash or fish. There are some
great features in zsh to help you get around the system.
Note: As a general rule fish has at least the same features as zsh, and
bash has fewer. So anything covered here for zsh should exist in fish, and
might have an equivalent in bash. zsh is 99% compatible with Bash, and thus
has almost no breaking changes from the original Bourne Shell (/bin/sh), so
it's a lot easier to use in parallel with other shells.
The autocd option

This is a great zsh feature. If you type a string into the terminal that
zsh doesn't recognise as a valid command, then it will try to parse it as a file
path and cd to it.
This means instead of typing cd foo you can just type foo (as long as that's
not also a command on your system, if it is just type foo/). It works for
absolute paths and handles variables as well, so you can type ~/foo, /foo, or
$XDG_CONFIG_HOME/foo, and they'll all work fine.
You can read more about it in the manual here, or in man zshoptions.
The CDPATH

When you pass cd a path that isn't an absolute path (starting with /), or a
relative path (starting with ./ or ../), cd will assume it's a relative
path starting from the current directory ($PWD). However you can also tell
cd to look in other directories if it's not in the current directory, by
setting the CDPATH variable. Like $PATH, $CDPATH is a colon-separated list
of paths. However whereas $PATH is the list of paths to check for binaries,
$CDPATH is the list of paths to check for arguments to cd.
Note that the $CDPATH variable is different in zsh and bash. In bash
you need to include the current directory at the beginning, otherwise cd will
prefer ~/foo to ./foo (which is rarely what you want in my experience). So I
set CDPATH=~ in zsh, and CDPATH=:~ in bash.
It's worth emphasizing that the CDPATH only works for cd (surprisingly!), so
you won't be able to take advantage of it for other operations like cp foo ~/tmp. This is one of the reasons I make sparing use of it, and only use it for
~. Adding an extra ~/ is only two extra characters, so it's not the end of
the world. The Tab-Completion shortcuts I'll cover next work everywhere.
Tab-Complete for Paths

This is the killer feature of zsh for me, and combined with the features above
it really shines. This is easiest to explain with an example. If I want to
get to ~/code/rust/xi-editor, I can just type c/r/x and then press
Tab. If there is only one possible combination with those paths then
it will directly complete to the full path, if there are multiple it will prompt
you to choose. This means it's suddenly very easy to cd into deeply nested
directories, and that in each directory it's best to have every subdirectory
start with a different letter.
Run command on chpwd

Allows you to run arbitrary commands whenever you change a directory. I just use
it to run ls -A every time I change directories, so I always know what's in
the directory I'm in.
More info here.
History for cd

setopt autopushd autocd pushd_silent pushdignoredups pushdminus
Lots of detail here, but the key ones are
autopushd and pushd_silent. autopushd makes cd work like pushd, which
keeps a history of the directories you have cd'd to. You can go back by typing
popd, or better by typing cd - Tab. This gives you an interactive
menu for which directory you want to go to. See the picture below for more info.
pushd_silent suppresses pushd's annoying habit of printing the path it is
cding to. However it doesn't stop the printing of the path that comes from
$CDPATH. What this means specifically (with my $CDPATH setup) is that it
prints the path whenever it's non-obvious where you're cding to, e.g. when
foo takes you to ~/foo not ./foo. This is the ideal balance between
explicit and minimalism in my (infallible) opinion.
Advanced Tab-Complete

It's quite complex to get this stuff to work, but you can do some fancy things
with the completion widget. I tell zsh to treat .,_- as
completion boundaries, but much more advanced completion is possible.
Laying out your file system
Given these features, it's important to lay out your filesystem to take
advantage of them. The first thing is to put everything in your home directory
(~/), which is good practice in general.
You then probably want one directory to put your code in, which doesn't share a
first letter with anything else in your home directory. I use ~/code for
personal stuff, and ~/wrk for work stuff, but you can use anything.
Within those directories you want to keep the pre-tab-complete paths as short as
possible, so to get to ~/code/node you only want to have to type
c/nTab. If you also have ~/code/npm then it won't work, but if
you have a ~/code/node-gyp it will (it completes to the shortest substring in
all possible completions, which in this case is ~/code/node, which is what you
want).
Basically if something you access rarely conflicts with something common, either
change the name to something with a different first letter (that you can
remember), or put it in a subdirectory (e.g. ~/code/other/npm), which can then
be accessed quickly (c/o/n).
Summary
With all these features you should never need to spend more than a second typing out a path.