Many years ago, back when I thought spending time configuring my Unix environment was a good idea, I wrote a script that I call fu. It helps manage long pathnames and is particularly useful with our tendency to have deep directory structures in perforce:
$ pwd /Users/drj/info.ravenbrook.com/project/mps/branch/2007-07-12/ramprel/code $ fu master /Users/drj/info.ravenbrook.com/project/mps/master $ cd `fu master` $ pwd /Users/drj/info.ravenbrook.com/project/mps/master
As you can see,
fu searches up the directory hierarchy (towards root) until it finds a match for its argument.
I think its code provides some interesting points of shell programming that are not always stressed:
# $Header: //depot/home/drj/master/prj/uxutils/fu#3 $ # fu - find up # finds a file who's name is / where x is specified on the # command line, and is some prefix (dirname) of the current # working directory. x="$1" d=`pwd` while : do case "X$d" in */) d=`echo "$d"|sed '$s/.$//'`;; esac if test -r "$d/$x" then echo "$d/$x" exit 0 fi n=`dirname "$d"` if test "X$d" = "X$n" then exit 4 fi d="$n" done exit 2
The first things to point out is all the double quotes around variable expansions, like this «”$d”». That’s done so that the variable expands into a single word even when it contains a space. This is almost always what you want. Consider a simple example like «ls $d», if d is the string «foo bar» (which contains a space) then this will ask
ls to list the two files «foo» and «bar», which is probably not what you wanted. Of course people that put spaces in their filenames, then pass those names as arguments to Unix programs, then expect it to work, are insane (are you reading this Apple? «Library/Application Support/»). But we do what we can. So unless there are very good reasons why not, every variable expansion gets double quotes around it.
The line that invokes
sed, «d=`echo “$d”|sed ‘$s/.$//’`», is pretty typical of string manipulation in shell: tedious, ugly, and sometimes obscure. All it’s doing is stripping a trailing «/» from d, but look how awkward it is. It invokes an external program and probably forks another shell; it could easily be a million times slower than the equivalent code in more traditional compiled language like C or Lisp. You don’t have to do very much of this sort of fiddly manipulation before it becomes very sensible to use a proper language like Python.
«test -r» is evidence of how old the script is (I think).
-r tests whether a file is readable, whereas all I care about is existence; clearly I should be using «test -e», but I suspect that at the time I originally wrote the script
-e was either not standard or not implemented widely enough. These days I should probably change it.
Those initial Xs in «if test “X$d” = “X$n”» are kind of curious. They’re there to prevent the kind of nonsense that happens if d happens to start with a hyphen and therefore confuse
test. If d happened to be «-x» for example, then we would have
test -x = something which might confuse
test into thinking that it should be executing the
-x test (test for an executable file). The version of
test on OS X doesn’t suffer this problem, but I’m pretty sure that earlier ones did. I’m pretty sure that the X in «case “X$d”» is there for the same reason, but that’s a bogus reason because
case doesn’t suffer this problem.
Hmm. Well, perhaps it would made for some interesting points of shell programming if two of my circumlocutions weren’t made obsolete by progress in Unix utility implementations.