Filename arguments passed to scripts: Are they absolute or relative?

relativity abhinaba basu
Credit: flickr / Abhinaba Basu

When you look at a file reference, it's easy to tell if it's an absolute or relative reference. You look at the first character. If it's a slash (/), it's absolute. If it's a tilde (~), it's also absolute because it will resolve to a file within someone's home directory. And, if it's anything else, it's relative.

Making the same decision within a script depends on that same kind of analysis. You just have to look at the first character in the argument string to determine whether the file argument provided refers to a file that is relative to the file system location from which the script is run or is an absolute file reference.

To nail down this determination, you can use some fairly simple logic. In the mini script below, we focus on the first character of the first argument to distinguish absolute from relative file references.

#!/bin/bash

if [[ ${1:0:1} == / || ${1:0:1} == ~ ]]; then
    echo absolute
else
    echo relative
fi

The "${1:0:1}" strings in this bit of code pluck off a substring of the argument provided which is only 1 character long. Basically, it starts with the first character (character 0) of the string and continues for a length of one character.

The complete script (shown at the bottom of this post) also demonstrates some other techniques that I think are helpful:

  • verifying that the expected argument has been provided (or prompting for it if it hasn't),
  • verifying that the argument provided represents a file that actually exists, and
  • setting up the script so that it copies the file to a fixed set of servers, but never to itself

The last point means that you can use the same script on any of a number of servers and have the file go out to all of the others.

The first step is something I generally do with any script that requires an argument. Having a choice means that the script can be run through cron (if you have the system set up for file transfers using scp and trusted credentials), but is flexible enough to accept an arbitrary argument.

if [ $# == 0 ]; then
    echo -n "file> "
    read file
else
    file=$1
fi

The second step gives an error and exits if the file argument doesn't refer to a file that exists on the system.

if [ ! -f $file ]; then
    echo "ERROR: No file named $file found"
    exit 1
fi

The third step, like the mini script we just looked at, evaluates the file argument and determines whether the argument provided is a relative path to the file or an absolute. The code here does the does the same thing as the code above except that it looks at the first character of $file instead of the first character of $1. If the file path is relative, it prepends $file with the current path so that the file can easily be copied to the same location on the other servers. For example, ~slee/myfile turns into something like /home/slee/myfile and ~/myfile turns into /home/myself/myfile.

if [[ ${file:0:1} != / && ${file:0:1} != ~ ]]; then
    path=`pwd`
    file=$path/$file
fi

And notice also that we went from using "or" (||) to "and" (&&) in our logic. So, instead of asking whether the first character is / or ~, we're asking if it's not equal to either of them -- since we only need to do something in that case.

The last part of the script copies the file in question to three of four servers, the fourth server being itself. It loops through the four server names, checks each one to see if it is the same as its own name -- using the -s (short) argument to make sure we're not comparing servername to servername.domain.com. And it then uses scp to copy the file in question to each server that isn't itself. The server names (e.g., server-1) have to be replaced with the actual names, of course.

# copy file to remote servers
for server in server-1 server-2 server-3 server-4
do
    if [ $server != `hostname -s` ]; then	# don't copy to self
        scp -p $file $server:$file
    fi
done

The nice thing about this technique is that you could use this script to copy a file from any of the servers to the other three without making any changes to the script. The "copy to everywhere but myself" logic makes this work.

Alternately, you could have the server names list in a separate file and change your syntax to "for server in `cat serverlist`", but you would have to keep the serverlist files synchronized on the servers as well. With this script, you just have to copy the script itself. And, of course, make sure you have your systems set up to trust each other or run the script only from one of the servers that the other trust. Or, of course, you can provide passwords as you loop through the server list.

#!/bin/bash

if [ $# == 0 ]; then
    echo -n "file> "
    read file
else
    file=$1
fi

if [ ! -f $file ]; then
    echo "ERROR: No file named $file found"
    exit 1
fi

if [[ ${file:0:1} != / && ${file:0:1} != ~ ]]; then
    path=`pwd`
    file=$path/$file
fi

# copy file to remote servers
for server in server-1 server-2 server-3 server-4
do
    if [ $server != `hostname -s` ]; then	# don't copy to self
        scp -p $file $server:$file
    fi
done

Making a change on one system and making sure that any other server that uses the same script gets the changes as well can be simplified with a script of this kind. I put this together in answer to a question posed by a reader. Reader questions are always welcome. Email me if you have one.

This article is published as part of the IDG Contributor Network. Want to Join?

To express your thoughts on Computerworld content, visit Computerworld's Facebook page, LinkedIn page and Twitter stream.
Windows 10 annoyances and solutions
Shop Tech Products at Amazon
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.