Reputation: 548
My script needs two arguments to run.
It can have optionals if needed so I want to ensure these properties so I have written the following script
if [ $# -lt 3 ]
then
echo "This command need 3 arguments"
else
echo $1 $2 $3
fi
observed that "$#" counts total number of arguments passed including the optionals
OUTPUT :
sh my_script.sh -o optional1 -r.
It works but not as intended as my check fails.
Expected Output :(two arguments are compulsory)
sh my_script.sh arg1 arg2 --> should work
sh my_script.sh -o optional1 -r optional2 --> throw error that it requires two arguments
sh my_script.sh -o optional1 -r optional2 arg1 arg2 --> should work
Upvotes: 1
Views: 981
Reputation: 84561
Note, $#
reports the number of arguments (positional parameters
). It doesn't care what they are. Anything on the command line is considered an argument. (e.g. somevar
or -o
). So to distinguish between -foo
and foo
, you will need to either write an interpreter, or use getopts
as provided by others (as that interpreter)
While getopts
is fine, I generally want my own interpreter. To provide a custom interpreter in sh
, you will generally use a while loop
, shift
and a case
statement to check each positional parameter and respond accordingly. While string manipulation is somewhat limited in sh
compared to bash
it is sufficient.
An example that fits your requirements could be:
#!/bin/sh
if [ $# -lt 2 ]; then
printf "This command need 2 arguments\n"
exit 1
fi
let nargs=0
while [ "$1" != "" ]; do
printf " processing: %s\n" "$1"
case "$1" in
-r ) shift
[ "$1" = "" -o `expr index "$1" "-"` -eq 1 ] && \
{ printf "error: -r requires an option.\n"; exit 1; }
r_var="$1"
printf " -r %s\n" "$r_var"
let nargs=$nargs+1
;;
-* ) ;; ## don't count '-x' arguments as options (change as needed)
* ) let nargs=$nargs+1
;;
esac
shift
done
if [ $nargs -lt 2 ]; then
printf "\n Two compulsory arguments not given.\n\n"
exit 1
else
printf "\n required arguments satisfied.\n\n"
fi
exit 0
Example Use/Output
$ sh params.sh arg1 arg2
processing: arg1
processing: arg2
required arguments satisfied.
$ sh params.sh -o optional -r
processing: -o
processing: optional
processing: -r
error: -r requires an option.
$ sh params.sh -o optional -r arg1 arg2
processing: -o
processing: optional
processing: -r
-r arg1
processing: arg2
required arguments satisfied.
$ sh params.sh -o arg2
processing: -o
processing: arg2
Two compulsory arguments not given.
Checking for Duplicate Arguments
A nested for
loop checking the positional parameters for duplicates is probably the best way to check for duplicate arguments if you don't want to try and cover everything in the case
statement. One approach would be to check at the beginning of your script:
#!/bin/sh
let args=2
## check duplicate arguments
for i ; do
for j in $(seq $args $#); do
printf "i: %s j: %s compare %s %s\n" $i $j $i ${!j} ## delete - just for info
if [ $i = ${!j} ]; then
printf "error: duplicate args [ %s = %s ], exiting.\n" $i ${!j}
exit 1;
fi
done
let args=$args+1
done
exit 0
Examples/Output
$ sh pdupes.sh a b c d e
i: a j: 2 compare a b
i: a j: 3 compare a c
i: a j: 4 compare a d
i: a j: 5 compare a e
i: b j: 3 compare b c
i: b j: 4 compare b d
i: b j: 5 compare b e
i: c j: 4 compare c d
i: c j: 5 compare c e
i: d j: 5 compare d e
$ sh pdupes.sh a b c d b e
i: a j: 2 compare a b
i: a j: 3 compare a c
i: a j: 4 compare a d
i: a j: 5 compare a b
i: a j: 6 compare a e
i: b j: 3 compare b c
i: b j: 4 compare b d
i: b j: 5 compare b b
error: duplicate args [ b = b ], exiting.
Preventing Accepting Argument Twice
In addition to the loop check above which will catch any duplicate, you can also add an option to the case
statement that will prevent accepting an argument twice. It is up to you whether you just want to skip the duplicate or flag it and exit. In the case of your -o
with optional argument, I would try something like the following which accepts an optional argument, tests it is not another -x
type and checks whether it was already set. If it passes all the tests, `o_var is set and by being set, it can't be set again:
-o ) shift
[ "$1" = "" -o `expr index "$1" "-"` -eq 1 -o ! -z $o_var ] && \
{ printf "duplicate value for '-o' received, exiting\n"; exit 1; } || \
o_var="$1" && let nargs=$nargs+1
printf " -o %s\n" "$o_var"
;;
Slight Variations Controlling Response to Duplicate
There are two additional ways of handling multiple attempts to set -o
. The first is to just exit on any second appearance of -o
in the argument list (regardless of whether there is an attempt to set the optional variable associated with -o
):
-o ) shift
## throw error on any second instance of '-o'
if [ -z "$o_var" ]; then
if [ ! `expr index "$1" "-"` -eq 1 -a "$1" != "" ]; then
o_var="$1"
let nargs=$nargs+1
printf " -o %s\n" "$o_var"
fi
else
printf "error: multiple attempts to set '-o', exiting.\n" "$o_var" "$1"
exit 1
fi
;;
Examples
Both attempts to set -o
a second time are treated the same:
$ sh params.sh -o foo -r arg1 arg2 -o bar
$ sh params.sh -o foo -r arg1 arg2 -o
processing: -o
-o foo
processing: -r
-r arg1
processing: arg2
processing: -o
error: multiple attempts to set '-o', exiting.
The other possibility is to only throw an error only if there is a second attempt to set the optional variable associated with -o
. (e.g. if the second -o
is harmless, ignore it):
-o ) shift
## throw error only if second instance of '-o' has optional argument
if [ "$1" != "" -a ! `expr index "$1" "-"` -eq 1 ]; then
if [ -z "$o_var" ]; then
o_var="$1"
let nargs=$nargs+1
printf " -o %s\n" "$o_var"
else
printf "error: multiple attempts to set '-o'. (%s, now '%s')\n" "$o_var" "$1"
exit 1
fi
fi
;;
Examples
$ sh params.sh -o foo -r arg1 arg2 -o bar
processing: -o
-o foo
processing: -r
-r arg1
processing: arg2
processing: -o
error: multiple attempts to set '-o'. (foo, now 'bar')
$ sh params.sh -o foo -r arg1 arg2 -o
processing: -o
-o foo
processing: -r
-r arg1
processing: arg2
processing: -o
required arguments satisfied.
Upvotes: 3
Reputation: 11216
Without getopts, but pretty much the same thing, i prefer this way because it is easier to add long args.
#!/bin/bash
while true;do
case $1 in
'-o'|'--optional')
optional1=$2
shift 2
;;
'-r')
optional2=$2
shift 2
;;
*)
break
;;
esac
done
if [[ $# -lt 2 ]];then
echo Too few args.
exit 1
fi
echo $optional1
echo $optional2
Obviously it could be more robust as this will take multiple of the same flags.
Upvotes: 2
Reputation: 753765
Process the options with getopts
.
r_flag=0
o_flag="default value"
while getopts o:r arg
do
case "$arg" in
(o) o_flag="$OPTARG";;
(r) r_flag=1;;
(*) echo "Unrecognized option $arg" >&2; exit 1;;
esac
done
After the loop doing that, use shift $(($OPTIND - 1))
to get rid of the processed options.
Now you can check that there are at least 2 more arguments:
if [ $# -lt 2 ]
then
echo "Usage: $(basename $0 .sh) [-o option][-r] arg1 arg2 ..." >&2
exit 1
fi
Upvotes: 2