Optional option argument with getopts
Solution 1
getopts
doesn't really support this; but it's not hard to write your own replacement.
while true; do
case $1 in
-R) level=1
shift
case $1 in
*[!0-9]* | "") ;;
*) level=$1; shift ;;
esac ;;
# ... Other options ...
-*) echo "$0: Unrecognized option $1" >&2
exit 2;;
*) break ;;
esac
done
Solution 2
Wrong. Actually getopts
does support optional arguments! From the bash man page:
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.
When the man page says "silent" it means silent error reporting. To enable it, the first character of optstring needs to be a colon:
while getopts ":hd:R:" arg; do
# ...rest of iverson's loop should work as posted
done
Since Bash's getopt does not recognize --
to end the options list, it may not work when -R
is the last option, followed by some path argument.
P.S.: Traditionally, getopt.c uses two colons (::
) to specify an optional argument. However, the version used by Bash doesn't.
Solution 3
This workaround defines 'R' with no argument (no ':'), tests for any argument after the '-R' (manage last option on the command line) and tests if an existing argument starts with a dash.
# No : after R
while getopts "hd:R" arg; do
case $arg in
(...)
R)
# Check next positional parameter
eval nextopt=\${$OPTIND}
# existing or starting with dash?
if [[ -n $nextopt && $nextopt != -* ]] ; then
OPTIND=$((OPTIND + 1))
level=$nextopt
else
level=1
fi
;;
(...)
esac
done
Solution 4
I agree with tripleee, getopts does not support optional argument handling.
The compromised solution I have settled on is to use the upper case/lower case combination of the same option flag to differentiate between the option that takes an argument and the other that does not.
Example:
COMMAND_LINE_OPTIONS_HELP='
Command line options:
-I Process all the files in the default dir: '`pwd`'/input/
-i DIR Process all the files in the user specified input dir
-h Print this help menu
Examples:
Process all files in the default input dir
'`basename $0`' -I
Process all files in the user specified input dir
'`basename $0`' -i ~/my/input/dir
'
VALID_COMMAND_LINE_OPTIONS="i:Ih"
INPUT_DIR=
while getopts $VALID_COMMAND_LINE_OPTIONS options; do
#echo "option is " $options
case $options in
h)
echo "$COMMAND_LINE_OPTIONS_HELP"
exit $E_OPTERROR;
;;
I)
INPUT_DIR=`pwd`/input
echo ""
echo "***************************"
echo "Use DEFAULT input dir : $INPUT_DIR"
echo "***************************"
;;
i)
INPUT_DIR=$OPTARG
echo ""
echo "***************************"
echo "Use USER SPECIFIED input dir : $INPUT_DIR"
echo "***************************"
;;
\?)
echo "Usage: `basename $0` -h for help";
echo "$COMMAND_LINE_OPTIONS_HELP"
exit $E_OPTERROR;
;;
esac
done
Solution 5
This is actually pretty easy. Just drop the trailing colon after the R and use OPTIND
while getopts "hRd:" opt; do
case $opt in
h) echo -e $USAGE && exit
;;
d) DIR="$OPTARG"
;;
R)
if [[ ${@:$OPTIND} =~ ^[0-9]+$ ]];then
LEVEL=${@:$OPTIND}
OPTIND=$((OPTIND+1))
else
LEVEL=1
fi
;;
\?) echo "Invalid option -$OPTARG" >&2
;;
esac
done
echo $LEVEL $DIR
count.sh -d test
test
count.sh -d test -R
1 test
count.sh -R -d test
1 test
count.sh -d test -R 2
2 test
count.sh -R 2 -d test
2 test
iverson
Updated on July 09, 2022Comments
-
iverson almost 2 years
while getopts "hd:R:" arg; do case $arg in h) echo "usage" ;; d) dir=$OPTARG ;; R) if [[ $OPTARG =~ ^[0-9]+$ ]];then level=$OPTARG else level=1 fi ;; \?) echo "WRONG" >&2 ;; esac done
level refers to the parameter of
-R
, dir refers to parameters of-d
when I input
./count.sh -R 1 -d test/
it works correctlywhen I input
./count.sh -d test/ -R 1
it works correctlybut I want to have it work when I input
./count.sh -d test/ -R
or./count.sh -R -d test/
This means that I want
-R
to have a default value and for the sequence of commands to be more flexible. -
gerardw almost 11 yearsNot in a meaningful way. If -R is the last argument it's not processed
-
Admin over 9 yearsFrom playing around with getopts it it looks like what does the job here is
let OPTIND=$OPTIND-1
not sure what is going on with the level tracking. Maybe its for another use from your code? -
calandoa about 8 yearsNo. YOU are wrong.
./count.sh -R -d test/
is not working because-d
is taken as the argument of-R
(which is not at all optional). -
johnnyB almost 8 yearsThe example given from wiki.bash-hackers.org does not work if the last option is the "optional" OPTARG.
$ prog.bash -a -b -c
will not even know about -c option when$ prog.bash -a -c -b
will. How can this work if its the last arg? -
Rohit almost 8 yearsPlease see answer below from Andreas Spindler , it is supported
-
user2141130 over 7 yearsThis answer is misleading at best. As pointed out by @calandoa, any option with an "optional" argument only "works" if it is the last option given. Otherwise, it will consume the next option as its argument. e.g. in this usage
./count.sh -R -d test/
'-R' takes '-d' as its argument and '-d' is not recognized as an option. I'm only restating what has already been said (by @calandoa) because this incorrect answer has 20 net upvotes. -
user2141130 over 7 yearsMaybe a previous version of bash (or sh) "does the right thing" but bash 4.3.30(1) Debian Jessie fails as described in the previous comment.
-
Otiel about 6 years@Rohit Please note that Andreas Spindler answer is wrong in most ways, as described in the comments below his answer.
-
Chuck Wilbur almost 6 yearsThis answer is a strange case in terms of earning upvotes in that it doesn't answer the rather specific question asked, but it does answer a (I'm guessing) much more common question: What is the syntax to get
getopts
to ignore arguments it doesn't recognize without reporting an error? That's the question I came seeking an answer to and this was the answer I was after. -
nandilugio almost 5 yearsLast test (
count.sh -R 2 -d test
) gives me1
as a result, not2 test
(bash 5.0.3). All others work. This is because${@:$OPTIND}
evaluates to all the rest of the arguments, not just the next. -
nandilugio almost 5 yearsThis is in fact the only of the answers here that works! Please upvote it.
-
nandilugio almost 5 yearsFixing it using @calandoa's answer makes it pass all the tests.
-
nandilugio almost 5 yearsInspired in this answer (the only one that actually works!), I've made a simple function that can make it easy to be used multiple times. See my answer here
-
Christoph almost 3 yearseval nextopt=\${$OPTIND} is a creative solution, but Bash already has a special syntax for indirect expansion: nextopt=${!OPTIND}.
-
tripleee over 2 years(There is no "above" or "below"; each visitor has their personal sorting preferences to decide in which order answers are displayed.)