Having getopts to show help if no options provided - bash

I parsed some similar questions posted here but they aren't suitable for me.
I've got this wonderful bash script which does some cool functions, here is the relevant section of the code:
while getopts ":hhelpf:d:c:" ARGS;
do
case $ARGS in
h|help )
help_message >&2
exit 1
;;
f )
F_FLAG=1
LISTEXPORT=$OPTARG
;;
d )
D_FLAG=1
OUTPUT=$OPTARG
;;
c )
CLUSTER=$OPTARG
;;
\? )
echo ""
echo "Unimplemented option: -$OPTARG" >&2
echo ""
exit 1
;;
: )
echo ""
echo "Option -$OPTARG needs an argument." >&2
echo ""
exit 1
;;
* )
help_message >&2
exit 1
;;
esac
done
Now, all my options works well, if triggered. What I want is getopts to spit out the help_message function when no option is triggered, say the script is launched just ./scriptname.sh without arguments.
I saw some ways posted here, implementing IF cycle and functions but, since I'm just starting with bash and I already have some IF cycles on this script, I would like to know if there is an easier (and pretty) way to to this.

If you just want to detect the script being called with no options then just check the value of $# in your script and exit with a message when it is zero.
If you want to catch the case where no option arguments are passed (but non-option arguments) are still passed then you should be able to check the value of OPTIND after the getopts loop and exit when it is 1 (indicating that the first argument is a non-option argument).

Many thanks to Etan Reisner, I ended up using your suggestion:
if [ $# -eq 0 ];
then
help_message
exit 0
else
...... remainder of script
This works exactly the way I supposed.
Thanks.

Dropping a sample code from #Etan Reisner's statement:
you want to catch the case where no option arguments are passed (but non-option arguments) are still passed then you should be able to check the value of OPTIND after the getopts loop and exit when it is 1 (indicating that the first argument is a non-option argument).
# Check if any arguments were passed
if [ $OPTIND -eq 1 ]; then
help_message
exit 1
fi
# ........ the rest of the script
Just in case someone wanted a sample visual 😅

Related

How to pattern match a script argument in Linux bash

Basically, I have to create a bash script that analyzez the arguments of the script and if it's only one, proceeds to pattern match it. If it starts with a number, it says so, if it starts with a letter, it says so and if neither, it says the argument is a string. If you didn't provide an argument, it says you need 1, or if you provided 2 or more. Everything works, apart from the pattern matching itself. I always get the default case. What's wrong with it?
I gotta use case :( .
case $# in
1) case $1 in
/^?[0-9][a-zA-z]*$/) echo "Argument starts with number";;
/^?[a-zA-z][a-zA-z]*$/) echo "Argument starts with letter";;
*) echo "Argument is a string";;
esac;;
0) echo -n "You can't use 0 arguments"
exit 1;;
*) echo -n "You can't use 2 or more arguments"
exit 1;;
esac
exit 0
EDIT: typing this in terminal gives me the default case: l#ubuntu:~/Documents$ ./e4_G_L.sh contor - maybe I wrote the argument wrong and the pattern matching works just fine?
As pointed out in the comments, case only supports globs (also known as wildcards). To check regular expression as in your script you could use [[ … =~ … ]] but that would be overkill. Since you only want to check the first letter, globs are sufficient.
Also, I would rephrase the warning message. Tell the user what to do, not just “You messed up. Good luck guessing on your next try.”.
if [ $# != 1 ]; then
echo "Expected 1 argument but found $#."
exit 1
fi
case "$1" in
[0-9]*) echo "Argument starts with number" ;;
[a-zA-Z]*) echo "Argument starts with letter" ;;
*) echo "Argument is a string";;
esac
Using bash extended patterns:
shopt -s extglob
case $# in
0) echo "You can't use 0 arguments"
exit 1
;;
1) case $1 in
[0-9]+([[:alpha:]]) ) echo "A number followed by letters" ;;
+([[:alpha:]]) ) echo "Only letters" ;;
*) echo "Something else" ;;
esac
;;
*) echo "You can't use 2 or more arguments"
exit 1
;;
esac
Thanks to everybody who bothered to help this lost soul :). It's a task for my Uni so no need for complex hints. The teacher knows what this is about so keeping it short. Ended up using the first answer.

bash getopts loop not iterating

I have a simple loop i use in a lot of my bash scripts, yet this particular one doesn't seem to work.
#!/bin/bash
function main
{
echo here
while getopts "Ah" cli_opt; do
case ${cli_opt} in
A)
echo "op A"
start_all
return $?
;;
*)
echo invalid option
showHep
exit 1
;;
h)
showHelp
exit 0
;;
\?)
invalid option
showHelp
exit 1
;;
:)
option -$OPTARG requires an argument
showHelp
exit 1
;;
esac
done
}
main
exit 0
Regardless of how i call it, it only reaches "here".
The fix, change:
main
to:
main "$#"
"$#" is an array containing all of the arguments. You'll need to pass this array to your main function.

Getopts: how to manager properly optional arguments?

I'm struggling with the following code.
#!/bin/bash
test-one () {
if [[ ! -z $1 ]] ; then
echo "You are in function test-one with arg $1"
else
echo "You are in function test-one with no args"
fi
}
while getopts ":a:b:" opt; do
case $opt in
a) test-one ${OPTARG}
exit;;
b) FOO=${OPTARG}
exit;;
esac
done
I's just like to call the function test-one whether the optional argument is passed or not.
What I am looking for is:
./script.sh -a argument1
would results in:
You are in function test-one with arg argument1
While:
./script.sh -a
would results in:
You are in function test-one with no args
Far by now the example "./script.sh -a" simply skip the function call ...
What am I doing wrong?
Thanks!
Regarding error-reporting, there are two modes getopts can run in:
verbose mode & silent mode
For productive scripts I recommend to use the silent mode, since everything looks more professional, when you don't see annoying standard messages. Also it's easier to handle, since the failure cases are indicated in an easier way.
Verbose Mode
invalid option VARNAME is set to ?(question-mark) and OPTARG is unset
required argument not found VARNAME is set to ?(question-mark), OPTARG is unset and an error message is printed
Silent Mode
invalid option VARNAME is set to ?(question-mark) and OPTARG is set to the (invalid) option character
required argument not found VARNAME is set to :(colon) and OPTARG contains the option-character in question
Try this:
#!/bin/bash
while getopts ":a:" opt; do
case $opt in
a)
echo "-a was triggered, Parameter: $OPTARG" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:) echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
When a -a is passed you will pass the value on the command line that is given as the argument to -a, stored in OPTARG. In your second example you there is no such argument, so the value gets an empty string, which is dutifully passed to the function.
Unfortunately here, a 0-length string is not preserved as an argument, and at least would in fact be a 0-length string which would "pass" the -z test.
So for "what you are doing wrong" it seems that you are expecting an empty argument to be treated as a non empty argument. You could test OPTARG before using it as an argument if you wanted to verify that -a did in fact have a value passed along with it.

How to parse invalid options?

I am writing the following script to parse some options:
#!/bin/bash
while [[ $# > 1 ]]
do
key="$1"
case $key in
-i|--inbound)
inbound="true"
shift # past argument
;;
-o|--outbound)
outbound="true"
shift # past argument
;;
*)
echo "hola"
exit 1
;;
esac
shift # past argument or value
done
echo $inbound
echo $outbound
The problem is that i would like to terminate the program, if I receive an invalid option, I tried the following:
*)
exit 1
echo "invalid option"
;;
but when i run the program like this: bash script.sh -invalid, anything happens, i would like to appreciate any suggestion to fix this, my desired output would like to:
invalid option
while [[ $# > 1 ]]
should be
while (($# >= 1))
You want a numeric comparison, not a string comparison. Although in this case, it doesn't make any difference, it would make a big difference if you had compared with 2, since the string 10 is less than the string 2.
Anyway, if you invoke your script with one argument, $# will be one. So the greater-than comparison is not correct.
Finally, if you really had:
exit 1
echo "hola"
the echo would never be executed, because the exit would have happened first.

Reading $OPTARG for optional flags?

I'd like to be able to accept both mandatory and optional flags in my script. Here's what I have so far.
#!bin/bash
while getopts ":a:b:cdef" opt; do
case $opt in
a ) APPLE="$OPTARG";;
b ) BANANA="$OPTARG";;
c ) CHERRY="$OPTARG";;
d ) DFRUIT="$OPTARG";;
e ) EGGPLANT="$OPTARG";;
f ) FIG="$OPTARG";;
\?) echo "Invalid option: -"$OPTARG"" >&2
exit 1;;
: ) echo "Option -"$OPTARG" requires an argument." >&2
exit 1;;
esac
done
echo "Apple is "$APPLE""
echo "Banana is "$BANANA""
echo "Cherry is "$CHERRY""
echo "Dfruit is "$DFRUIT""
echo "Eggplant is "$EGGPLANT""
echo "Fig is "$FIG""
However, the output for the following:
bash script.sh -a apple -b banana -c cherry -d dfruit -e eggplant -f fig
...outputs this:
Apple is apple
Banana is banana
Cherry is
Dfruit is
Eggplant is
Fig is
As you can see, the optional flags are not pulling the arguments with $OPTARG as it does with the required flags. Is there a way to read $OPTARG on optional flags without getting rid of the neat ":)" error handling?
=======================================
EDIT: I wound up following the advice of Gilbert below. Here's what I did:
#!/bin/bash
if [[ "$1" =~ ^((-{1,2})([Hh]$|[Hh][Ee][Ll][Pp])|)$ ]]; then
print_usage; exit 1
else
while [[ $# -gt 0 ]]; do
opt="$1"
shift;
current_arg="$1"
if [[ "$current_arg" =~ ^-{1,2}.* ]]; then
echo "WARNING: You may have left an argument blank. Double check your command."
fi
case "$opt" in
"-a"|"--apple" ) APPLE="$1"; shift;;
"-b"|"--banana" ) BANANA="$1"; shift;;
"-c"|"--cherry" ) CHERRY="$1"; shift;;
"-d"|"--dfruit" ) DFRUIT="$1"; shift;;
"-e"|"--eggplant" ) EGGPLANT="$1"; shift;;
"-f"|"--fig" ) FIG="$1"; shift;;
* ) echo "ERROR: Invalid option: \""$opt"\"" >&2
exit 1;;
esac
done
fi
if [[ "$APPLE" == "" || "$BANANA" == "" ]]; then
echo "ERROR: Options -a and -b require arguments." >&2
exit 1
fi
Thanks so much, everyone. This works perfectly so far.
: means "takes an argument", not "mandatory argument". That is, an option character not followed by : means a flag-style option (no argument), whereas an option character followed by : means an option with an argument.
Thus, you probably want
getopts "a:b:c:d:e:f:" opt
If you want "mandatory" options (a bit of an oxymoron), you can check after argument parsing that your mandatory option values were all set.
It isn't easy... Any "optional" option arguments must actually be required as far as getopts will know. Of course, an optional argument must be a part of the same argument to the script as the option it goes with. Otherwise an option -f with an optional argument and an option -a with a required argument can get confused:
# Is -a an option or an argument?
./script.sh -f -a foo
# -a is definitely an argument
./script.sh -f-a foo
The only way to do this is to test whether the option and its argument are in the same argument to the script. If so, OPTARG is the argument to the option. Otherwise, OPTIND must be decremented by one. Of course, the option is now required to have an argument, meaning a character will be found when an option is missing an argument. Just use another case to determine if any options are required:
while getopts ":a:b:c:d:e:f:" opt; do
case $opt in
a) APPLE="$OPTARG";;
b) BANANA="$OPTARG";;
c|d|e|f)
if test "$OPTARG" = "$(eval echo '$'$((OPTIND - 1)))"; then
OPTIND=$((OPTIND - 1))
else
case $opt in
c) CHERRY="$OPTARG";;
d) DFRUIT="$OPTARG";;
...
esac
fi ;;
\?) ... ;;
:)
case "$OPTARG" in
c|d|e|f) ;; # Ignore missing arguments
*) echo "option requires an argument -- $OPTARG" >&2 ;;
esac ;;
esac
done
This has worked for me so far.
For bash, this is my favorite way to parse/support cli args. I used getopts and it was too frustrating that it wouldn't support long options. I do like how it works otherwise - especially for built-in functionality.
usage()
{
echo "usage: $0 -OPT1 <opt1_arg> -OPT2"
}
while [ "`echo $1 | cut -c1`" = "-" ]
do
case "$1" in
-OPT1)
OPT1_ARGV=$2
OPT1_BOOL=1
shift 2
;;
-OPT2)
OPT2_BOOL=1
shift 1
;;
*)
usage
exit 1
;;
esac
done
Short, simple. An engineer's best friend!
I think this can be modified to support "--" options as well...
Cheers =)
Most shell getopts have been annoying me for a long time, including lack of support of optional arguments.
But if you are willing to use "--posix" style arguments, visit bash argument case for args in $#
Understanding bash's getopts
The bash manual page (quoting the version 4.1 manual) for getopts says:
getopts optstring name[args]
getopts is used by shell scripts to parse positional parameters. 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. The colon (‘:’) and question mark (‘?’) may not be
used as option characters. Each time it is invoked, getopts places the next
option in the shell variable name, initializing name if it does not exist, and the
index of the next argument to be processed into the variable OPTIND. OPTIND
is initialized to 1 each time the shell or a shell script is invoked. When an
option requires an argument, getopts places that argument into the variable
OPTARG. The shell does not reset OPTIND automatically; it must be manually
reset between multiple calls to getopts within the same shell invocation if a
new set of parameters is to be used.
When the end of options is encountered, getopts exits with a return value
greater than zero. OPTIND is set to the index of the first non-option argument,
and name is set to ‘?’.
getopts normally parses the positional parameters, but if more arguments are
given in args, getopts parses those instead.
getopts can report errors in two ways. If the first character of optstring is a
colon, silent error reporting is used. In normal operation diagnostic messages
are printed when invalid options or missing option arguments are encountered.
If the variable OPTERR is set to 0, no error messages will be displayed, even if
the first character of optstring is not a colon.
If an invalid option is seen, getopts places ‘?’ into name and, if not silent,
prints an error message and unsets OPTARG. If getopts is silent, the option
character found is placed in OPTARG and no diagnostic message is printed.
If a required argument is not found, and getopts is not silent, a question mark
(‘?’) is placed in name, OPTARG is unset, and a diagnostic message is printed. If
getopts is silent, then a colon (‘:’) is placed in name and OPTARG is set to the
option character found.
Note that:
The leading colon in the option string puts getopts into silent mode; it does not generate any error messages.
The description doesn't mention anything about optional option arguments.
I'm assuming that you are after functionality akin to:
script -ffilename
script -f
where the flag f (-f) optionally accepts an argument. This is not supported by bash's getopts command. The POSIX function getopt() barely supports that notation. In effect, only the last option on a command line can have an optional argument under POSIX.
What are the alternatives?
In part, consult Using getopts in bash shell script to get long and short command-line options.
The GNU getopt (singular!) program is a complex beastie that supports long and short options and supports optional arguments for long options (and uses GNU getopt(3). Tracking its source is entertaining; the link on the page at die.net is wrong; you'll find it in a sub-directory under ftp://ftp.kernel.org/pub/linux/utils/util-linux (without the -ng). I've not tracked down a location at http://www.gnu.org/ or http://www.fsf.org/ that contains it.
#!/bin/bash
while getopts ":a:b:c:d:e:f:" opt; do
case $opt in
a ) APPLE="$OPTARG";;
b ) BANANA="$OPTARG";;
c ) CHERRY="$OPTARG";;
d ) DFRUIT="$OPTARG";;
e ) EGGPLANT="$OPTARG";;
f ) FIG="$OPTARG";;
\?) echo "Invalid option: -"$OPTARG"" >&2
exit 1;;
: ) echo "Option -"$OPTARG" requires an argument." >&2
exit 1;;
esac
done
echo "Apple is "$APPLE""
echo "Banana is "$BANANA""
echo "Cherry is "$CHERRY""
echo "Dfruit is "$DFRUIT""
echo "Eggplant is "$EGGPLANT""
echo "Fig is "$FIG""

Resources