Shell programming: The fu example

2007-08-29

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.

About these ads

4 Responses to “Shell programming: The fu example”

  1. drj11 Says:

    It occurs to me to explain another oddity:

    The colon in «while :» is a shell builtin that does nothing; it returns 0 so the while loop runs forever (until the break breaks out). The colon builtin is not nearly well known enough. The colon builtin behaves almost (or exactly?) the same way as true, and it would certainly be clearer to use «while true»; I suspect I used to use a system where true was an external command not a builtin so «while :» will have been faster.

  2. chodonsona Says:

    a=30
    b=40
    echo “3rd number”
    read c
    x=’expr $a/* $b/* $c’
    echo “$x”

    it shows error

  3. Matthew Bentham Says:

    I always thought that the X in:

    if test “X$d” = “X$n”
    and
    case “X$d” in

    was to avoid something bad happening if the variable was empty?

  4. drj11 Says:

    @Matthew: Could be. It’s (happily) becoming difficult to get hold of rusty old versions of proprietary Unix where the shell was broken in all sorts of ways.

    To clarify: today, nothing bad does happen when the variable is empty. That’s what the double quotes are for and they do their job.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: