2020-01-31
Finding out what directories exist with only basic shell builtins (a Unix shell trick)
Back in the old days of multi-architecture Unix environments, generally no two Unix vendors could
agree on what should be in your $PATH
. The core of /bin and
/usr/bin was the same, but everyone had their own additional
directories (Solaris had a lot of them).
In addition, different local computing groups had different views
on where local programs should go; /usr/local/bin, /local/bin,
/opt/<something>/bin, /<group>/bin, and so on. This was a problem
for me because I maintained a common set of dotfiles across all
of the Unix systems I had accounts on,
and I didn't want my $PATH
to be a giant list of every possible
directory on every system. So I needed to trim down a giant master
list of possible $PATH
directories to only the ones that existed
on the current system, and to make it harder I wanted to use only
shell builtins, in a shell
where test
wasn't a builtin.
Fortunately, there is one thing that has to be a builtin and has
to fail if a directory doesn't exist (or isn't usable by you),
namely cd
. Using cd
as a substitute for 'test -d
' is a bit
odd, but it works. My shell has
real lists, so I could write this as more or less:
# potential $PATH entries in $candidates path=`{ tpath=() for (pe in $candidates) builtin cd $pe >[1=] >[2=] && tpath=($tpath $pe) echo $tpath }
(This doesn't work for relative paths, but I didn't have any in
my $PATH
.)
Because all shells have to have cd
as a builtin, this same trick
could be used in pretty much any shell. Bourne style shells make
you work a bit harder to put together $PATH
; at a minimum you
need to stick :'s between every element (cf), and maybe your equivalent
of $candidates
also uses :'s to separate entries and so you have
to split it up on those.
(In a Bourne shell I would make $candidates
just be quoted and space
separated, because that's a lot simpler to deal with. This wouldn't
handle a $PATH
entry that had spaces in it, but you don't normally see
those.)
Using cd
this way is a trick, but tricks are what you're forced
into in a minimal shell environment where you don't want to run
external programs. In actual practice, I wound up writing a little
C program to do this and relying on that on systems that I used
frequently enough to compile it for them. The implementation with
cd
was only a fallback for systems and situations without my
isdirs
program.
(This is sort of a followup to a Unix shell trick, which was also about something that I had to do for cross-architecture dotfiles.)
PS: All of this comes from the days when systems were slow enough that
you tried to avoid running additional external programs in your shell
dotfiles, which is why I wanted to do it all with shell builtins instead
of running a lot of test
s or the like. And most modern shells have
test
as a builtin anyway.