Skip to content

Blog Zero

16 Feb 2022

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 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 a latefees subdirectory then here we could just run the command rp dl lates without having to first change back to the original directory (which admittedly would only require running the command r).

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 and lsrp output long and short listings, respectively, of the contents of the directory whose pathname was output by the last rp command that was executed (so basically ls -l "$rp" and ls "$rp", respectively),
  • lrr and lsrr are similar, except that they operate on the directory that was the current one before the latest r command changed it (and so are similar to executing ls -l "$rr" and ls "$rr", respectively), and
  • rr is just an alias for r ., 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 and see if it's of interest to you, pass it along to anyone you think might be interested in it, or both. Thanks!

Tags: shell software