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