Skip to content
Advertisement

How to validate the number of arguments (mandatory and optional) in a shell script?

I am having trouble validating my script arguments. I am trying to achieve 5 mandatory arguments and 2 optional arguments to my script. Here is what I have tried so far.

#!/bin/sh

if [ $# -lt 5 ] || [ $# -gt 7 ]
then
  echo "Please supply 5 or 6 or 7 parameters. Aborting."
  exit 1
fi

echo MP1 = "$1"
echo MP2 = "$2"
echo MP3 = "$3"
echo MP4 = "$4"
echo MP5 = "$5"

while getopts c:a: optionalargs
do
        case $optionalargs in
          c)copt=$OPTARG;;
          a)aopt=$OPTARG;;
          *)echo "Invalid arg";;
        esac
done

if [ ! -z "$copt" ]
then
    export CHAR_SET=$copt
fi

if [ ! -z "$aopt" ]
then
    export ADDITIONAL_FLAGS=$aopt
fi

shift $((OPTIND -1))

echo OP_C = "${CHAR_SET}"
echo OP_A = "${ADDITIONAL_FLAGS}"

The problem is validating the total number of arguments to this script as I can have a total of 5 or 7 arguments. Providing -a additional -c character is treated as 9 arguments.

./a.sh 1 2 3 4 5 -a additional -c character
Please supply 5 or 6 or 7 parameters. Aborting.

I am open to designs with no - as long as I am able to have both mandatory and optional parameters.

How to get this properly validated?

Advertisement

Answer

First: the shell doesn’t know anything about optional vs. mandatory arguments, doesn’t treat arguments that start with “-” specially, anything like that. It just has a list of “words”, and it’s up to your script to figure out what they mean. The getopts command can help with parsing the arguments, but it handles a fairly limited syntax:

  • Options start with a single dash, and are a single letter (maybe with an argument after that). For options that don’t take arguments, you can stack multiple options on a single dash (e.g. ls -la).
  • After all the options, there can be a number of positional parameters (the meaning of these is defined — as the name implies — by their position in the list, e.g. first, second, etc).

There are a number of syntax extensions (mostly GNU conventions) that getopts does not support:

  • Putting options after positional parameters (e.g. ls filename -l). Options must always come first.
  • Long options (multi-letter and/or double-dash, e.g. ls --all).
  • Using -- to separate the options from the positional parameters.

So if you want to use getopts-style optional arguments, you need to put them first in the argument list. And when parsing them, you need to parse and remove* them from the argument list (with shift) before you check the number of positional parameters, and use $1 etc to access the positional parameters. Your current script is running into trouble because it’s trying to handle the positional parameters first, and that won’t work with getopts (at least without some heavy-duty kluging).

If you need a more general argument syntax, you might be able to use getopt (note the lack of “s” in the name). Some versions of getopt support the GNU conventions, some don’t. Some have other problems. IMO this is a can of worms that’s best left unopened.

Another possibility is to abandon the - option syntax, and give the script 7 positional parameters where the last two can be omitted. The problem with this is that you can’t omit the sixth but pass the seventh (unless you’re willing to consider the sixth being blank as equivalent to omitting it). The code for this would look something like this:

if [ $# -lt 5 ] || [ $# -gt 7 ]
then
  echo "Please supply 5 or 6 or 7 parameters. Aborting."
  exit 1
fi

echo "MP1 = $1"
echo "MP2 = $2"
echo "MP3 = $3"
echo "MP4 = $4"
echo "MP5 = $5"

if [ -n "$6" ]; then
    # This will run if a sixth argument was specified AND it wasn't blank.
    export CHAR_SET=$6
    echo "OP_C = ${CHAR_SET}"
fi

if [ $# -ge 7 ]; then
    # This will run if a seventh argument was specified EVEN IF it was blank.
    # If you want to omit this for a blank arg, use the `-n` test instead.
    export ADDITIONAL_FLAGS=$7
    echo "OP_A = ${ADDITIONAL_FLAGS}"
fi

…and then run the script with e.g.

./a.sh 1 2 3 4 5 character additional    # Both optional args supplied
./a.sh 1 2 3 4 5 character               # Only first optional arg supplied
./a.sh 1 2 3 4 5 "" additional           # Only second optional arg supplied

Or, if you really want the more extended syntax and don’t want to risk the vagaries of the getopt command, you can spend a lot of time and effort writing your own parsing system. IMO this is way more work than it’s worth.

Advertisement