Relocation: Additional
In an earlier post I covered the basics of using the main commands provided by my relocate package to navigate the filesystem more quickly and efficiently. Now we're going to discuss some additional features of those commands, as well as some auxiliary commands that are provided by the package. Then I'll introduce a few shell aliases and functions that are built on top of the package's commands, and that I find make moving around the filesystem even easier.
Auxiliary Commands and Environment Variables
One of the problems with relocation aliases is remembering which one aliases
a given directory, especially when you only recently defined the alias or you
only use it infrequently. The rg
command addresses that problem by
displaying all of the relocation aliases, along with the directories they
alias, where one or both match a specified regular expression. For example,
if you can't remember the relocation alias that you defined for the
/var/log
directory then you can just run
rg log
to display all of the relocation aliases that alias directories containing
log
(or that themselves contain log
, or both). It's basically a
convenient way to grep
your ~/.relocations
file.
The other auxiliary commands depend to a greater or lesser extent on a
feature of the relocation commands that we haven't mentioned yet: in addition
to switching directories they can also set some environment variables to the
pathnames of directories involved in their operation. For instance, when the
r
command is used to switch to a different directory then by default it
also
- sets the environment variable named
r
to the pathname of the directory that it just switched to, and - sets the environment variable named
rr
to the pathname of the directory that was the current directory just before it switched to the new one.
(See the comment at the top of the relocation-functions.sh
file in the
repository for information about how to prevent the various relocation
commands from setting any environment variables.) The environment variables
can then be used in commands as shorthands for those directories. As an
example, suppose you're in a deeply-nested directory and you want to copy
there all of the text files in the latest
subdirectory of your downloads
directory. You could do it by switching to the latest
subdirectory using
the r
command (something like r dl lat
if we assume that you've already
defined dl
to be a relocation alias for your downloads directory) and then
running a command like
cp *.txt $rr
there. (You could then run the r
command with no argument to return to the
original deeply-nested directory.) If you had wanted to copy the files to the
parent directory of the deeply-nested directory instead then you could
replace the above cp
command with
cp *.txt $rr/..
(Note that you'd want to write the $rr
and $rr/..
parts of those commands
as "$rr"
and "$rr/.."
, respectively if the value of the rr
environment
variable could contain spaces or other whitespace characters.)
Since the values of the r
and rr
environment variables can change every
time you (directly or indirectly: as part of a shell alias or function, for
example) execute the r
command you might not always be completely confident
of their current values. When that happens you can use the re
auxiliary
command to display the names and values of all of the environment variables
that can be set by a relocation command, and that are defined in the current
shell. It doesn't take any arguments: it always displays all of the relevant
environment variables. Its output might look something like
r /home/jgm/Downloads rr /home/jgm
if you had been in your home directory and then executed the command r dl
.
There's another auxiliary command named rp
that just outputs to standard
output the pathname of the directory that the r
command would actually
switch to if it were given the same arguments. Which can be helpful for doing
a "trial run" of an actual r
command, but otherwise isn't especially
useful. However it also sets some environment variables of its own, which can
be very handy indeed. For example, if we revisit the scenario above where we
wanted to copy text files from the latest
subdirectory of your downloads
directory to the current, deeply-nested directory we could use the fact that
the rp
command sets the rp
environment variable to the pathname of the
directory that it outputs. Then we could copy the files using these commands
instead:
rp dl lat cp $rp/*.txt .
(or cp "$rp/*.txt" .
if you're being careful). Doing it this way has a
couple of small advantages:
- you don't have to change the current directory at all, and
- it's easier to recover if
dl lat
doesn't match the directory that you thought it would: for example, if your downloads directory turned out to also have alatefees
subdirectory then here we could just run the commandrp dl lates
without having to first change back to the original directory (which admittedly would only require running the commandr
).
I think the main reason why I find myself using the rp
command and
environment variable instead of the r
and/or rr
variables is that I
rarely use the rp
command for any other reason, and so I can be much more
confident that its value will be the one that I expect (without having to use
the re
command to check).
The rp
command can also set the r1
and r2
environment variables to hold
the output of earlier uses of it: the output of the second- and third-most
recent rp
commands, if any, will be stored in r1
and r2
, respectively.
Note that none, some or all of rp
, r1
and r2
can have the same value:
they're set regardless of whether their values will be the same as one of the
others (which makes their values more predictable).
There are a few other auxiliary commands, but I don't find myself using them much, if at all:
lrp
andlsrp
output long and short listings, respectively, of the contents of the directory whose pathname was output by the lastrp
command that was executed (so basicallyls -l "$rp"
andls "$rp"
, respectively),lrr
andlsrr
are similar, except that they operate on the directory that was the current one before the latestr
command changed it (and so are similar to executingls -l "$rr"
andls "$rr"
, respectively), andrr
is just an alias forr .
, which (if you remember from the basics post) means it's sometimes a slightly faster way to switch to a directory in or under the current one.
As examples of that last command being a little faster, suppose that the
current directory only has one subdirectory starting with ch
: then even
with tab completion typing the command r ch
is a little quicker than typing
cd ch<TAB>
. And if you instead wanted to switch to the sole subdirectory of
that subdirectory that starts with gr
then typing r ch gr
is quicker
still than typing cd ch<TAB>gr<TAB>
.
Possibly because the savings in keystrokes was so slight, I ended up almost
never using the rr
command, at least until I (effectively) made 0
an
alias for it (see below). Despite it only being a single character shorter
(and despite rr
's second character being the same as its first) I use 0
constantly, to the point where it's all but completely replaced my use of the
cd
command in such situations.
Additional Commands
To make switching directories even easier you can define shell aliases or
functions that can be used to switch to specific directories, or directories
under those directories. For example, if you did a lot of work in the
/usr/local
directory and its subdirectories (and their subdirectories …)
then you could define a relocation alias ul
for it, and also a shell alias
like
alias ul='r ul'
that would let you switch to the directory /usr/local
just by executing the
command ul
. In addition, the command ul l
would presumably relocate you
to /usr/local/lib
and ul sh m .1
might very well switch you to the
directory /usr/local/share/man/man1
(depending on what other directories
exist under /usr/local/
).
While such aliases can be of use to a select group of people, I've come up
with some that I think could be useful to anyone. They're not part of the
relocate package itself, but you can define them by adding the following to
the file where you define shell aliases (like ~/.bashrc
, or possibly a
separate file like ~/.aliases
):
alias 1='r 1' alias 2='r 2' alias 3='r 3' alias 4='r 4' alias 5='r 5' alias 6='r 6' alias 7='r 7' alias 8='r 8' alias 9='r 9' alias 10='r 10'
(You can omit some of the later aliases, but directory hierarchies can get to
be deeper than you'd think.) These aliases assume that your ~/.relocations
file contains the 1
, 2
, …, 10
relocation aliases that are defined in
the .relocations.example
file in the repository: if it does then the above
shell aliases can be used to quickly move "up and over" to sibling and
similar directories. For example, if you're in the src
subdirectory of a
project's directory and you want to relocate to that project's docs
subdirectory then instead of running the command
cd ../docs
you can just run
1 d
(assuming that docs
is the only (or at least the first) subdirectory of the
project directory that starts with d
). Which is only a little shorter, but
if instead you had started somewhere deeper under the src
subdirectory like
src/static/css
then instead of having to enter the command
cd ../../../docs
you could just run the much more concise
3 d
Similarly, one could move from the directory /usr/local/src/emacs-26.3
to
/usr/include/X11
by running 3 in X
instead of cd ../../../include/X11
(which is a significant improvement even allowing for the use of tab/filename
completion on the last two components of the destination directory's
pathname).
One also shouldn't underestimate the convenience of running one-character
commands like 1
, 2
and 3
in place of cd ..
, cd ../..
and
cd ../../..
, respectively. Especially if it turns out that you run them
anywhere near as often as I seem to.
Attentive readers may have noticed that I haven't defined the 0
alias that
I mentioned above, though they may now have a pretty good idea of why it's
named that. Just as
- the
1
alias goes up one level to the parent directory, - the
2
alias goes up two levels to the "grandparent" directory, - and so on
(possibly before going down again), the 0
alias goes "up" zero levels to
the current directory before going down again. Which is just another way of
saying that it switches to a directory under the current directory. (Clearly
running the 0
command with no arguments isn't very useful.)
Thus — like in the rr
command examples above — if you ran the command
0 ch
then the (first) subdirectory of the current directory that starts
with ch
would become the current directory, and if instead you ran the
command 0 ch gr
then you'd be relocated to the (first) subdirectory of that
subdirectory that starts with gr
.
While you could simply define 0
as a shell alias using a definition like
alias 0='r .'
for somewhat obscure reasons you might actually want to define it as a shell function like this:
function 0() { r . "${@%/}" }
which will remove any trailing slashes from the ends of its arguments, under
the assumption that they were added by tab/filename completion. Unlike with
the 1
, 2
, etc. commands/aliases, if you inadvertently (or otherwise) use
tab completion on an argument to 0
then at least the first argument will
always be a valid one, except that the trailing slash (/
) that can (will?)
be added by bash's default tab completion will cause the underlying r
command to fail. But the above shell function prevents such failures,
allowing such uses of the 0
command to succeed after all. Whew!
A lot of the improvements suggested here and above can seem like insignificant savings of a few keystrokes, but in addition to their making frequently run commands quicker and easier to type, thus adding up to bigger savings over time, they also reduce the friction involved in switching to another directory to start another task. And getting started on a task can often end up being one of the more difficult parts of completing it.
If you'd like to support this site then check out my web app InEveryNook.com and see if it's of interest to you, pass it along to anyone you think might be interested in it, or both. Thanks!