Wednesday, October 18, 2023

Bash 101: Relative Pathing

While preparing for an interview in 1992 for a co-operative education program at IBM to support their new RISC SYSTEM/6000 line of servers and it's version of the UNIX operating system, Advanced Interactive eXecutive (AIX), I was introduced to what would become one of my favorite past times, shell scripting.  At that time, the common shells were the Bourne shell (sh), Korn shell (ksh), and C shell (csh).  Later, Linux would introduce what is now the default shell of many UNIX distributions, the Bourne-Again shell (bash).

Though shell scripting is technically not a programming language, everyone that works in the UNIX domain becomes familiar with shell scripting at some level.  Over my 30+ year career in IT, I've easily contributed more than 10,000,000 lines of shell scripting code to the projects that I've worked on.  The purpose of this blog is to pass along some of the gems that I've collected along the way.

When working on projects that include multiple scripts, one of the biggest problems is how do you write all of the scripts such that they will work properly in any directory path. For example, my current project is composed of 84 different scripts.   The answer, is that you define a common path context for all of the scripts in the header of every script in the project.  My standard for this is the following:

#!/bin/bash
cmd=$(basename $0)
curdir=$(dirname $0)
curdir=$(cd ${curdir}; pwd)

Let's unpack this simple set of lines to understand what's going on.

1. Define the shell to use for this script's runtime environment via the shebang directive.

#!/bin/bash

2. Determine the script name.  The $0 variable is the execution path of the script being run.  For example, the execution path could have been to run the script in the current directory with ./myscript.sh.  Or, it could have been called from a directory above with ../myscript.sh.  Or, perhaps it might have been called using the full path of /opt/ods/poc/myscript.sh.  The basename command trims off the path to leave just the script name.  We assign the output of the basename command to the cmd variable so that it can be used as a common reference (${cmd}) throughout the rest of the script.

cmd=$(basename $0)

3. Determine the base directory based of the script's location.  The dirname command trims off the script name leaving just the path to the script.  We temporarily assign the result of the dirname output to the curdir in the first step to determining the current directory.

curdir=$(dirname $0)

4. Lastly, we change directory to the current value of ${curdir} and then use the present working directory (pwd) command to determine the fully qualified path of the directory that we are in.

curdir=$(cd ${curdir}; pwd)

You may be wondering why we didn't just stop at step 3 because technically, that is correctly the current directory.  The reason is because the execution of the script can be called from a variety of ways that are not the full path name.  For example, if I ran the script with ../../../myscript.sh, the first value of curdir would be  "../../../", which is not the fully qualified path.  However, after step 4, the fully qualified path is righly determined to be /opt/ods/poc.

I hope you find this helpful!

Blessings!

Brad



No comments: