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.