Bash getopts dropping last argument - bash

So, I'm trying my hand at using bash's built-in getopts to handle argument processing except I'm getting a strange result. Here's my test script;
#!/bin/sh
HOST=
OWNER=
GROUP=
while getopts "h:o:g" OPTION;
do
case $OPTION in
h)
HOST=$OPTARG
;;
o)
OWNER=$OPTARG
;;
g)
GROUP=$OPTARG
;;
esac
done
echo "$HOST - $OWNER:$GROUP"
Yet, when I run the script using this;
./test.sh -h test.host.com -o skittles -g whatever
My last arg never gets pulled in or is getting dropped. My result from the echo is;
test.host.com - skittles:
^ where's my group value? O.o
Does anyone know what would be causing this?
Thanks.

It seems your expect -g to have an argument, but in your options declaration, there is no ":" related to your -g.
You should have this:
h:o:g:

Your option string is missing a : after the g.

Related

Flag argument is another flag

Trying to read my flag and their arguments,
I came accros a case where passing a flag with an expected argument,
fallowed by another flag instead of the expected argument,
would result in the second flag being interpreted as argument for the first flag.
WWW_ALIAS=0
while getopts ':d:a:w' flag; do
case "${flag}" in
d)
DOMAIN_NAME=${OPTARG}
;;
a)
IFS=',' read -ra DOMAIN_ALIASES <<< "${OPTARG}"
;;
w)
WWW_ALIAS=1
;;
:)
echo "[Error] Argument for option -${OPTARG} was omitted." 1>&2
exit 1
;;
\?)
echo "[Warning] Option ${OPTARG} is not supported and will be ignored." 1>&2
;;
esac
done
if [ -z "${DOMAIN_NAME}" ]; then
echo "[Error] Domain parameter (-d) must be set" 1>&2
exit 1
fi
Thus, running ./script.sh -w -d will trigger the error message at the end of this code example.
But running ./script.sh -d -w will instead assign -w to the DOMAIN_NAME variable while it shouldn't.
Is there a way to make sure that any flag can't be used as argument for a flag ?
The supported syntax for getopts is:
a - option -a without value; error on unsupported options
a: - option -a with value; error on unsupported options
ab - check for options -a, -b; error on unsupported options
:ab - check for options -a, -b; silences errors on unsupported options
Since you specified d:, the argument parser will check for an additional value after the switch -d. Not specifying one, just like in this command: ./script.sh -w -d will trigger an error from the argument parser (not your code).
Since you specified w without a (: after it), the argument parser will NOT check for an additional value after the switch -w. Hence you don't see an error for that flag.
When you run with -d -w, the parser sees only the first switch, which is -d, and it consumes the next token as the argument, which is the expected outcome.
As there a way to make sure that any flag can't be used as argument for a flag?
Yes, there are a couple of options actually.
Option 1: inside the menu, add a sanity check to allow only reasonable values:
...
case "${flag}" in
d)
if [[ "${OPTARG}" == -* ]]; then
echo "Bad argument!"
exit 1
fi
DOMAIN_NAME=${OPTARG}
;;
...
Option 2: Use eval set -- "$OPTS"
Some don't like this option because eval is evil. But it's still an option if you are not afraid of this attack vector.
This command will sort the arguments and will prevent things like this from happening.
Find an example here

how to pass other arguments besides flags

I am trying to execute my file by passing in an absolute path as the first argument ($1). I also want to add flags from that absolute path onward, but i do not know how to tell optargs to start counting from $2 forward since if i pass in the absolute path as the $1 it seems to break the getopts loop.
I'm gussing i have to implement a shift for the first argument in the following code:
while getopts :lq flag; do
case $flag in
l) echo "executing -l flag"
;;
q) echo "executing -q flag"
;;
esac
done
I'm not sure how to approach this. Any tips are welcome, thank you.
getopts does, indeed, stop processing the arguments when it sees the first non-option argument. For what you want, you can explicitly shift the first argument if it is not an option. Something like
if [[ $1 != -* ]]; then
path=$1
shift
fi
while getopts :lq flag; do
...
done
Keep the options before file argument (i.e. absolute path).
Many standard bash commands follow the same practice.
Example :
wc -wl ~/sample.txt
ls -lR ~/sample_dir
So if you follow the above practice, your code goes like this.
This code works even if options are not provided.
In general, that is the desired behavior with options.
# Consider last argument as file path
INPUT_FILEPATH=${*: -1}
echo $INPUT_FILEPATH
# Process options
while getopts :lq flag
do
case $flag in
l) echo "executing -l flag"
;;
q) echo "executing -q flag"
;;
esac
done
Sample execution :
bash sample.sh /home/username/try.txt
/home/username/try.txt
bash sample.sh -lq /home/username/try.txt
/home/username/try.txt
executing -l flag
executing -q flag

Issue with getops parameter

Currently, I'm working on a bash script that is meant to have parameters passed through it.
My getOps lines:
while getopts ":s:d:e:*" opt; do
case $opt in
s)
kb_status
;;
d)
kb_disable
;;
e)
kb_enable
;;
*)
echo "Invalid option: -$OPTARG"
;;
esac
done
The main issue is whenever I try to pass the script through
./myscript.sh -e`
I get the following message from my wildcard parameter:
Invalid option: -e
However, when I run it as
./myscript.sh -ee
or have any second letter in the parameter, it passes perfectly fine. Can someone help me fix this issue?
The problem is the ":" character after the e in
while getopts ":s:d:e:*" opt; do
The ":" tells getopts to expect an argument after the -e option.
So if you want your script to just support -s -d and -e options, then do the following:
while getopts sde opt; do
Putting : after e in the option list means that the -e option requires an argument. -e by itself is missing the argument, -ee sets the value of the argument to e.
Since you don't do anything with $OPTARG, it looks like you don't really require arguments to your options, so you shouldn't be using : after each of them.
while getopts ":sde*" opt; do
It's also unclear why you have * at the end of the option list. That will allow "-*", but the case block will report that as an error.

Bug in parsing args with getopts in bash

I was trying to modify the bd script to use getopts. I am a newbie at bash scripting
my script is
while getopts ":hvis:d:" opt
do
...
done
...
echo $somedirpath
cd "$somedirpath"
this runs fine when doing
$ ./bd -v -i -s search
or
$ ./bd -is search -d dir
But when running it like this
$ . ./bd -s search
getopts doesn't read the arguments at all. And all the variables I set in the while loop according to the arguments are all not set, so the script no longer works. Please help!
Setting OPTIND=1 before invoking getopts works fine.
The problem is that getopts relies on OPTIND to loop through the arguments provided, and after sourcing the script, it will be set to some value greater than 1 by getopts according to how many arguments you pass. This value gets carried over even after the script ends(because its being sourced). So the next time its sourced, getopts will pick up from that OPTIND, rather than starting from 1!
This might cause strange behaviour with other scripts, and I don't know how safe this is. But it works!
For a better workaround, I think what #tripleee suggests looks safe and robust.
When you source a script, the arguments parsed by getopts are those of the current shell, not the parameters on the source command line.
The common workaround is to have your script merely print the path, and invoke it like cd "$(bd)" instead (perhaps indirectly through a function or alias).
Setting OPTIND=1 may not work reliably on zsh. Try to use something different than getopts:
while [ "$#" -gt 0 ]
do
case "$1" in
-h|--help)
help
return 0
;;
-o|--option)
option
return 0
;;
-*)
echo "Invalid option '$1'. Use -h|--help to see the valid options" >&2
return 1
;;
*)
echo "Invalid option '$1'. Use -h|--help to see the valid options" >&2
return 1
;;
esac
shift
done

How can I prevent an option that requires an argument to use the next option when no argument is given?

I have the following code in test.sh:
while getopts "f:i:" opt; do
case $opt in
f)
echo $OPTARG
i) echo $OPTARG
Now if I run ./test.sh -f I will get the error:
option requires an argument -- i
However, when I run ./test.sh -f -i test it will echo -i.
I know that this is because it just gets the next argument separated by a space, but is there an easy way to handle this?
I could do if [ $OPTARG == "-i" ]; then exit 1 but I'm hoping there is an easier way for when I have multiple options.
If you are using getopts, it has it's own ways. Just go with it.
After all, who says the option's argument cannot begin with a dash? If it's a filename, maybe the user wants the filename to begin with a dash. If it's a number, maybe it is a negative number.

Resources