bash getopts loop not iterating - bash

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.

Related

Bash optarg fails to spot missing arguments

I am inexperienced with bash shell scripting, and have run into a problem with bash optarg
Here's a small script to reproduce the problem:
#!/bin/sh
while getopts ":a:b:" opt; do
case ${opt} in
a ) echo "a=$OPTARG"
;;
b ) echo "b=$OPTARG"
;;
\? ) echo "Invalid option: $OPTARG" 1>&2
;;
: ) echo "Invalid option: $OPTARG requires an argument" 1>&2
esac
done
When I try this:
./args.sh -a av -b bv
I get the expected result:
a=av
b=bv
But when I omit the argument for -a:
/args.sh -a -b bv
I get this unfortunate result:
a=-b
When I would expect an error to show that the value of -a is missing.
It seems to have taken the -b argument as the value for -a.
Have I done something wrong & how can I achieve the expected behaviour?
The only positive advice is how do you treat But when I omit the argument for '-a', you cannot just skip to the next subsequent option. By convention getopts a: means you are expecting to an provide an arg value for the flag defined.
So even for the omitting case, you need to define an empty string which means the value for the arg is not defined i.e.
-a '' -b bv
Or if you don't expect the -a to get any arg values, better change the option string to not receive any as :ab:.
Any other ways of working around by checking if the OPTARG for -a is does not contain - or other hacks are not advised as it does not comply with the getopts() work flow.
getopts doesn't support such detection. So there's no way to do that with getopts.
You can probably write a loop around the arguments instead. something like:
#!/bin/sh
check_option()
{
case $1 in
-*)
return 1
;;
esac
return 0
}
for opt in $#; do
case ${opt} in
-a) shift
if check_option $1; then
echo "arg for -a: $1"
shift
else
echo "Invalid option -a"
fi
;;
-b) shift
if check_option $1; then
echo "arg for -b: $1"
shift
else
echo "Invalid option -b"
fi
;;
esac
done

Extending getopts from sourced script

I am trying to extend the getopts from the sourced script as below.
if an option other than a,b,c,d is passed in, it needs to print "invalid option" and exit.
script_a.sh:
#!/bin/bash
script_a_getopts() {
while getopts 'a:b:' OPT; do
case "$OPT" in
a)
a=$OPTARG
;;
b)
b=$OPTARG
;;
*)
echo "invalid option"
exit 1
;;
esac
done
}
script_b.sh
#!/bin/bash
source script_a.sh
while getopts ':c:d:' OPT; do
case "$OPT" in
c)
c=$OPTARG
;;
d)
d=$OPTARG
;;
[?])
script_a_getopts $#
esac
done
echo "a=$a"
echo "b=$b"
echo "c=$c"
echo "d=$d"
When I run the script, it don't work as expected, obviously I am making a mistake.
$ ./script_b.sh -c cat -d dog -a apple -b boy
a=
b=
c=cat
d=dog
Didn't throw error when -x is passed.
$ ./script_b.sh -x
a=
b=
c=
d=
Short Answer: You have to rollback the OPTIND before calling script_a_getopts.
case "$OPT" in
c)
c=$OPTARG
;;
...
[?])
let OPTIND--
script_a_getopts $#
esac
done
Long Answer:
The getopts track which arguments have been processed using the OPTIND variable. When the top getopts recognized unknown item, it has already 'consumed' it by moving OPTIND to the next argument. To get script_a_getopts to process that argument, the OPTIND need to be rolled back to point to the unprocessed argument.
The let OPTIND-- will allow the unrecognized argument to be re-processed.
Side note, if you want to allow options to be placed in arbitrary order (-c cat -a apple -d dog -b boy) you will have to repeat the same in the script_a_getopts. This will require improved error processing in script_a_getopts 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.

Having getopts to show help if no options provided

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 😅

Bash interprets too long arguments for an option

I'm writing a bash script, that can take three options: c, p, and m; c can't have arguments, but p and m must have arguments. In my script I have these lines of code:
while getopts ":cp:m:" opt
do
case $opt in
c ) capitals=1
;;
m ) length=$OPTARG
;;
p ) number=$OPTARG
;;
\? ) echo "ERROR" 1>&2
exit 1
;;
esac
done
shift `expr $OPTIND - 1`
Now, when I write in the command line
myScript.sh -c -p 15 -m 12
all goes perfect. But when I write
myScript.sh -cp15m12
it goes wrong. Bash interprets the argument of the option 'p' as "15m12", while it should just be "15".
How can I solve this?
Since command-line arguments are not typed, there is no way to tell bash that the argument to -p must be an integer, and therefore no way for bash to infer that the correct way to parse -cp15m12 is as -c -p15 -m12 rather than as -c -p15m12. Since -c does not take an argument and getopts cannot define multi-character options, bash can infer that -cp15 is split into two separate options, -c and -p.
The value of getopts is fairly limited -- it's not hard to write your own replacement which supports option clustering. Here's a quick ad-hoc attempt.
#!/bin/sh
while true; do
case $1 in
-*) opt=${1#-}; shift;;
*) break;;
esac
while [ "$opt" ]; do
case $opt in
'-') opt=""; break 2;;
c*) capitals=1; opt=${opt#c};;
m[0-9]*)
opt=${opt#m}; length=${opt%%[!0-9]*}; opt=${opt#$length};;
m) opt=""; length=$1; shift;;
p[0-9]*)
opt=${opt#p}; number=${opt%%[!0-9]*}; opt=${opt#$number};;
p) opt=""; number=$1; shift;;
h|\?) echo "Syntax: $0 [-c] [-m length] [-p number]" >&2; exit 1;;
*) echo "Invalid option '$opt'" >&2; exit 2;;
esac
done
done
cat <<HERE
capitals: '$capitals'
length: '$length'
number: '$number'
args: '$#'
HERE
(Not sure if break 2 is entirely compatible with stock sh. Works for me on dash.)

Resources