Testing whether or not a flag has an argument - bash

My typical setup for parsing command-line options is:
CONF=""
INPUT=""
while getopts ":c:i:" FLAG; do
case $FLAG in
i) INPUT=$OPTARG;;
c) CONF=$OPTARG;;
\?) echo -e "\nInvalid option: -$OPTARG"
usage;;
:) echo -e "Option -$OPTARG requires an argument."
usage;;
esac
done
if [ "$#" -eq 0 ]; then
usage
fi
I'm looking for a way to catch when a valid flag is provided but no argument is - for example:
./Script.sh -c -i
Returns usage. I was under the impression that this line:
:) echo -e "Option -$OPTARG requires an argument."
Handled this however when running the script as above using flags without arguments, the usage function is not firing nor is the echo.
What am I doing wrong?

If you invoke your script with either ./script.sh -c -i -c or ./script.sh -c, both will show the error message Option -c requires an argument.
However, when invoking ./script.sh -c -i, you are passing value "-i" for the -c argument, so that at the end of arguments parsing, you end up with CONF=-c and INPUT not set.

Related

Using getopts in Bash

I want to use getopts in a Bash script as follows:
while getopts ":hXX:h" opt; do
case ${opt} in
hXX ) Usage
;;
h ) echo "You pressed Hey"
;;
\? ) echo "Usage: cmd [-h] [-p]"
;;
esac
done
The idea behind is that I want to have two flags -h or --help for allowing user to be able to use HELP in order to be guided how to use the script and another second flag which starts with h but its like -hxx where x is whatever.
How can I distinguish these two since even when I press --hxx flag it automatically executes help flag. I think the order of presenting them in getopt has nothing to do with this.
The 'external' getopt program (NOT the bash built in getopts) has support for '--longoptions'. It can be used as a pre-procssor to the command line options, making it possible to consume long options with the bash built-in getopt (or to other programs that do not support long options).
See: Using getopts to process long and short command line options for more details.
#! /bin/bash
TEMP=$(getopt -l help -- h "$#")
eval set -- "$TEMP"
while getopts h-: opt ; do
case "$opt" in
h) echo "single" ;;
-) case "$OPTARG" in
-help) echo "Double" ;;
*) echo "Multi: $OPTARG" ;;
esac ;;
*) echo "ERROR: $opt" ;;
esac
done

Can a shell script flag have optional arguments if parsing with getopts?

I have a script that I want to run in three ways:
Without a flag -- ./script.sh
With a flag but no parameter -- ./script.sh -u
With a flag that takes a parameter -- ./script.sh -u username
Is there a way to do this?
After reading some guides (examples here and here) it doesn't seem like this is a possibility, especially if I want to use getopts.
Can I do this with getopts or will I need to parse my options another way? My goal is to continue using getopts if I can.
The non-getopts example in BashFAQ #35 can cover the use case:
user_set=0 # 1 if any -u is given
user= # set to specific string for -u, if provided
while :; do
case $1 in
-u=*) user_set=1; user=${1#*=} ;;
-u) user_set=1
if [ -n "$2" ]; then
user=$2
shift
fi ;;
--) shift; break ;;
*) break ;;
esac
shift
done

How make bash getopts not recognize option as a option argument

In my bash script, I would like to use getopts to parse command-line options.
My first attempt, to learn how to use it, is as follows:
#!/bin/bash
v_option_arg=""
r_option_arg=""
h_option_arg=""
function get_opt_args() {
while getopts ":v:r:h:" opt
do
case $opt in
"v")
v_option_arg="$OPTARG"
;;
"h")
h_option_arg="$OPTARG"
;;
"r")
r_option_arg="$OPTARG"
;;
"?")
echo "Unknown option -$OPTARG"
exit 1
;;
":")
echo "No argument value for option -$OPTARG"
;;
*)
# Should not occur
echo "Unknown error while processing options"
;;
esac
done
shift $((OPTIND-1))
}
get_opt_args $#
if [ ! -z "$v_option_arg" ]; then
echo "Argnument value for option -v: $v_option_arg"
fi
if [ ! -z "$r_option_arg" ]; then
echo "Argnument value for option -r: $r_option_arg"
fi
if [ ! -z "$h_option_arg" ]; then
echo "Argnument value for option -h: $h_option_arg"
fi
$ bash testopts.sh -v 1
Argnument value for option -v: 1
$ bash testopts.sh -r 2
Argnument value for option -r: 2
$ bash testopts.sh -h 3
Argnument value for option -h: 3
$ bash testopts.sh -v 1 -r 2 -h 3
Argnument value for option -v: 1
Argnument value for option -r: 2
Argnument value for option -h: 3
$ bash testopts.sh -v
No argument value for option -v
$ bash testopts.sh -a
Unknown option -a
This seems to work successfully.
Next, I test my script's robustness by omitting an argument:
$ bash testopts.sh -v -r 2
Argnument value for option -v: -r
This is not what I was expecting. How can I make it distinguish the differences of one option and one option argument?
I want to make my script more robust, so that if one option is given without its argument, I can emit a suitable error message.
Note: Each option must have a option argument.
Can I do this with just getopts?
From man bash, SHELL BUILTIN COMMANDS, getopts:
optstring contains the option characters to be recognized; if a character is followed by a colon, the option is expected to have an argument, which should be separated from it by white space.
From your script:
while getopts ":v:r:h:" opt
You told bash explicitly that -v expects an argument. In the case of -v -r 2, -r is the argument to -v, with 2 remaining as non-option argument to the script.
Works as designed, and this is the limit of getopts abilities.
What you can do is checking if the argument to -v is numeric (as it seems that is what your script expects), and in the given case inform the user that -v does require a number, and that -r isn't it. But that is something your script needs to do in the "v") case, not something getopts can handle.
case $opt in
"v")
v_option_arg="$OPTARG"
if [[ ! "${v_option_arg}" =~ ^[0-9]*$ ]]
then
echo "Error: Option '-v' expects numeric argument, you gave: ${v_option_arg}"
exit 1
fi
;;

make getopts error when optionstring does not start with a dash

I am writing a script that uses getopts for options that require arguments and some that do not. I want getopts to exit with an error if any switches do not start with a '-', rather than simply stop parsing the options and continue. So if this is my getopts line:
while getopts "a:e:f:o:q:r:djpsvV" opt; do
and I call the script like this:
script.sh -a word -o eat me -j -d -e testing
the script stops parsing at "me" and the remaining switches are ignored. I want it to error out when it reaches "me", because it does not start with a '-'. How do I do that?
After your while getopts loop, do this:
shift $((OPTIND-1))
if (( $# > 0 )); then
echo "error: extra args detected: $*" >&2
exit 1
fi

Parsing mixed arguments in a script bash

I need to implement a script called with mixed (optional and non-optional) arguments for example -
./scriptfile -m "(argument of -m)" file1 -p file2 -u "(argument of -u)"
in a random order. I've read a lot about the getopts builtin command, but I think it doesn't solve my problem. I can't change the order of arguments, so I don't understand how I can read the arguments one by one.
Someone have any ideas?
You should really give a try to getopts, it is designed for that purpose :
Ex :
#!/bin/bash
while getopts ":a:x:" opt; do
case $opt in
a)
echo "-a was triggered with $OPTARG" >&2
;;
x)
echo "-x was triggered with $OPTARG" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
esac
done
Running the script with different switches ordering :
$ bash /tmp/l.sh -a foo -x bar
-a was triggered with foo
-x was triggered with bar
$ bash /tmp/l.sh -x bar -a foo
-x was triggered with bar
-a was triggered with foo
As you can see, there's no problem to change the order of the switches
See http://wiki.bash-hackers.org/howto/getopts_tutorial
Consider using Python and its excellent built-in library argparse. It will support almost any reasonable and conventional command line options, and with less hassle than bash (which is, strangely, a fairly poor language when it comes to command line argument processing).

Resources