Monday, December 30, 2013

Unix: When a bash script asks "Where am I?"

http://www.itworld.com/operating-systems/386139/unix-when-bash-script-asks-where-am-i

When a question like "How can a bash script tell you where it's located?" pops into your head, it seems like it ought to be a very easy question to answer. We've got commands like pwd, but ... pwd tells you where you are on the file system, not where the script you are calling is located. OK, let's try again. We have echo $0. But, no, that's not much better; that command will only show you the location of the script as determined by how you or someone else called it. If the script is called with a relative pathname like ./runme, all you will see is ./runme. Obviously if you are running a script interactively, you know where it is. But if you want a script to report its location regardless of how it is called, the question gets interesting.
So as not to keep you in suspense, I'm going to provide the answer to this question up front and then follow up with some insights into why this command works as it does. To get a bash script to display its location in the file system, you can use a command like this:
echo "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
That's something of a "mouthful" as far a Unix commands go. What exactly is going on in this command? We're clearly echoing something and using the cd and the pwd command to provide the information. But what's going on with this command?
One thing worth noting is that the command uses two sets of parentheses. These cause the script to launch subshells. The inner subshell uses ${BASH_SOURCE[0]} which is the path to the currently executing script, as it was invoked. The outer subshell uses the cd command to move into that directory and pwd to display the location. Since these commands are subshells, nothing has changed with respect to the rest of the script. We just invoke the subshells to display the information we're looking for and then continue with the work of the script.
To get a feel for how subshells work, we can use one to run a command that changes to a different directory and displays that location. When the command is completed, we're still where we started from.
$ echo $(cd /tmp; pwd)
/tmp
$ pwd
/home/shs/bin
This is not entirely unlike what our location-reporting command is doing; it's just one level simpler.
Clearly, other vital information concerning a script can be displayed using a series of echo commands -- all related to where we are when we run the script and how we call it.
If we run a script like the "args" script shown below, the answers will reflect how the script was invoked.
#!/bin/bash

echo "arguments ---->  ${@}"
echo "\$1 ----------->  $1"
echo "\$2 ----------->  $2"
echo "path to me --->  ${0}"
echo "parent path -->  ${0%/*}"
echo "my name ------>  ${0##*/}"
For the two path variables, what we see clearly depends on how we call the script -- specifically, if we use a full path name, a variable will represents the full path (such as ~), or a relative path.
$ ~/bin/args first second
arguments ---->  first second
$1 ----------->  first
$2 ----------->  second
path to me --->  /home/shs/bin/args
parent path -->  /home/shs/bin
my name ------>  args
$ ./args first second
arguments ---->  first second
$1 ----------->  first
$2 ----------->  second
path to me --->  ./args
parent path -->  .
my name ------>  args
You can use the location-reporting command in any script to display its full path. It will, however, follow and display symbolic links if they are used to invoke the script. Here, we see that a symlink points at our bin directory, but the script reports on the symlink:
$ ls -l scripts
lrwxrwxrwx 1 shs staff 5 Dec  7 18:36 scripts -> ./bin
$ ./scripts/args
arguments ---->
$1 ----------->
$2 ----------->
path to me --->  ./scripts/args
parent path -->  ./scripts
my name ------>  args
arguments ---->
$1 ----------->
$2 ----------->
path to me --->  ./scripts/args
parent path -->  ./scripts
my name ------>  args
When you use the location-reporting command, you get the full path for a script even if you call it with a relative path. Here's an example of a script that does nothing else:
#!/bin/bash

echo "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
And here's the result. We call the script with ./wru (for "where are you") and the output will look something like this. Voila! We get the full path even though we invoked the script with a relative path:
$ ./wru
/home/shs/bin
The $BASH_SOURCE variable may seem like a one that's just popped into existence, but it's actually one of a number of bash variables, many of which are likely very familiar. But, as you'd guess from the [0] included in the command above, it's an array.
A bash reference such as this will provide some additional information on this and other bash variables:
http://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html

UPDATE

Thanks to readers for there feedback. Looks like any of the following commands will work to display the location of a bash script.

No comments:

Post a Comment