fcd

:date: 2023-09-28 14:08 :tags:

Today I got a little frustrated with what I consider to be a small shortcoming in the Bash shell. I'm not criticizing Bash in any way because true to its spirit, I was able to use Bash to create the solution that eliminates the problem permanently for me. It's an interesting and illustrative example of the thinking involved and it might actually be a practical solution you can use.

The problem comes from tab completion. People ignorant of command line methodology usually mistakenly suppose I must keep a lot of things in my head  —  like file system layouts. No. I'm not that clever. On the contrary, what we Bash power users tend to do is spam the tab key while we blunder around our filesystem by brute force using tab completion. This works fine almost always, but there is one case that is ever so slightly annoying.

Sometimes you are bumbling around your directory tree looking for a particular file  —  not a directory (path location)  —  but a non-directory file. Tab completion certainly is good at this and you can quickly find files if you have a rough idea of where they might be. If I wanted to delete a particular file, I could start off with the rm command and tab my way to it and hit enter and it would be done. Great.

But what if the command is cd (Change Directory)? What if you want to find a particular file and then change to the directory it turns up in?

If you tab complete to something like this...

cd /tmp/a/b/c/d/e/file.txt

...and press enter, you'll get an error saying this is not a directory (not a d that one can c to).

bash: cd: /tmp/a/b/c/d/e/file.txt: Not a directory

(Overly technical side note: I'm discovering that Debian bullseye, with Bash-5.1.4, won't even let you tab complete to a file when you have the line starting with cd. Something to do with "Programmable Completion" and the complete builtin - look those up in man bash and have a look at /etc/bash_completion. But on newer Debian bookworm, with Bash-5.2.15, the behavior has changed! I totally can tab to files with cd as the command. This is probably why this is bugging me only just now.)

Ok, no big deal, right? You just hit the up arrow (experts use command history as much as tab completion!), queue up the malformed attempt and then spam the backspace key to get rid of the file name. (Or if you can remember fussy Emacs key bindings better than I can: Alt-b to go back a word, and Alt-d to delete a word or Ctl-k to delete to the end of the line; I'm sure there are other ways).

The more serious version of this problem is when you have a long and/or obnoxious file name. I personally never put spaces in file names just to avoid this kind of nuisance, but other people are not so accommodating. Imagine I just tab completed my way to this gem from CDDB (line breaks for clarity).

cd /home/xed/music/classical/Various-Classical Collection/2-3-13.London
    Wind Orchestra  Holst - Suite No. 2 for Wind Band Fourth Movement -
    Fantasia on the Dargason-London Wind Orchestra  Holst - Suite No. 2
    for Wind Band Fourth Movement - Fantasia on the Dargason.mp3

That command would fail. And that is the problem.

And this is the solution I came up with.

fcd () { I=$1;if ! [[ -d "$I" ]];then I=$(dirname "$1");fi && cd $I;}

If you put this Bash function in your Bash environment somehow (e.g. just cut and paste it right in, or put it in your ~/.bashrc, etc) you can use it the way we want. You can tab complete to a file and it will change your directory to the directory containing that file.

I strategically called it fcd for *f*ile *c*hange *d*irectory and this allows us to use our old familiar cd without much thinking. If you get to the situation shown above where you are trying to cd to something and you tab complete to a file, you can just press enter and get the error. Then you can hit up arrow to bring the previous command back, press Ctl-a to move the cursor to edit the beginning of that line, and type the f (for file) and hit enter and you're done. So it's a way to recover very quickly from this slight annoyance.

I'm happy with the solution and it works fine for cases I can think of. It is a good example of how in Bash there really aren't unsolvable problems that should be solvable. Cool.

If you've followed along so far, you understand the problem and motivation and even the eventual solution. If you're a modern computer nerd in 2023, you should be wondering: how did GPT4 do with this problem? Well, I knew there was a solution and was pretty sure what it was. But I'm lazy and if you know there is a short and sweet solution to a computer nerd problem, our robot friends can often be quicker at finding it.

However. In this case GPT4 was a complete failure. Right away I could tell that input containing spaces was going to trip this up (and we just saw that's quite a likely use case). Look at what happened when I alerted it to the problem.

fcdquotes.png

There's wrong and then there's silly wrong. You don't need to have any idea what this code does to see that the exact same code is not going to be some kind of improvement.

The idea to use $@ is critically flawed. GPT4 also bungled the proper usage of the dirname command. I investigated it and it was a little surprising. (Really? It eats the last subdirectory when no file is present? Yes it does.) Then again, I'm not a walking man page. After reading the short man page, it made sense and I knew what to do, which is more than we can say for GPT4.

If this post seemed too technical to you, that's fine. Hopefully you can appreciate that I'm taxing GPT with a slightly more esoteric topic. And it can not keep up! If you're as knowledgeable as David MacKenzie (one of the authors of dirname), or even me, you can see that the time to be worried about being completely replaced by our robot friends is not quite now.