Confessions of a Wall Street Programmer

practical ideas (and perhaps some uncommon knowledge) on software architecture, design, construction and testing

Where Am I?

From Robinson Crusoe to Gilligan’s Island to Lost, tales of being stranded on a desert island seem to resonate with people in a special way. Some of that likely has to do with the exotic locales, and the practical challenges of getting water, food and shelter.

But an even more basic part is the unanswered question: “Where am I?” that makes things so – well, mysterious.

Shell scripting can be pretty mysterious too at times, but in this installment we’ll learn how to answer that basic question of “Where am I?” to make shell scripting a little less mysterious.

One of the tenets of the Unix way is brevity, and one consequence of that is that well-behaved programs should be able to find whatever other resources they need without having to be told where they are. Windows attempts to solve this problem with the (gack!) registry, but Unix tends to use a simpler approach: needed resources are placed either in well-known locations (e.g., /etc for system programs), or where they can be found relative to the location of the program itself.

Another attribute of a well-behaved Unix program is that it should be able to run from any location, whether it’s invoked with a full path, or found via the PATH variable.

So, how do we reconcile those two requirements? And specifically, how do we do that in shell scripts? Since – regardless of what your “main” language is, if you’re programming in Unix/Linux, you’re probably also writing a boatload of shell scripts too.

It turns out that, at least in bash, there is a simple but non-obvious way to do get the location of the script file itself, which goes something like this:

SCRIPT_DIR=$(cd $(dirname ${BASH_SOURCE}) && /bin/pwd) 

Let’s follow this through and see how it works:

  • The $( ... ) construct invokes a sub-shell. This is handy since it allows us to change the environment of the sub-shell (e.g., current directory) without affecting the current environment.

  • $BASH_SOURCE is a builtin variable that gives us the path to the shell script itself. For instance, if we invoke a script with ./, then that’s what will end up in ${BASH_SOURCE}.

  • To get the full path then we extract just the path part with dirname, again in a sub-shell.

  • We then cd into that directory, and if successful get the full pathname with /bin/pwd.
    • Note that we use /bin/pwd to get the path. This version resolves any symbolic links to return the actual physical path. There is also a pwd built-in to bash, but that one does not expand symbolic links by default.
  • Finally, the result is assigned to SCRIPT_DIR.

We now have the full path of the script file itself, and can use that to locate any other resources needed by the script. For a real-world example, you can check out the these scripts from my earlier post on visualizing latency.