while getopts \"hd:R:\" arg; do
case $arg in
h)
echo \"usgae\"
;;
d)
dir=$OPTARG
;;
R)
if [[ $OPTARG =~ ^[0-9]+$ ]];then
I think there are two way.
First is calandoa's answer, Using OPTIND and no silent mode.
Second is Using OPTIND and silent mode.
while getopts ":Rr:" name; do
case ${name} in
R)
eval nextArg=\${$OPTIND}
# check option followed by nothing or other option.
if [[ -z ${nextArg} || $nextArg =~ ^-.* ]]; then
level=1
elif [[ $nextArg =~ ^[0-9]+$ ]]; then
level=$nextArg
OPTIND=$((OPTIND + 1))
else
level=1
fi
;;
r)
# check option followed by other option.
if [[ $OPTARG =~ ^-.* ]]; then
OPTIND=$((OPTIND - 1))
level2=2
elif [[ $OPTARG =~ ^[0-9]+$ ]]; then
level2="$OPTARG"
else
level2=2
fi
;;
:)
# check no argument
case $OPTARG in
r)
level2=2
;;
esac
esac
done
echo "Level 1 : $level"
echo "Level 2 : $level2"
You can always decide to differentiate the option with lowercase or uppercase.
However my idea is to call getopts
twice and 1st time parse without arguments ignoring them (R
) then 2nd time parse only that option with argument support (R:
). The only trick is that OPTIND
(index) needs to be changed during processing, as it keeps pointer to the current argument.
Here is the code:
#!/usr/bin/env bash
while getopts ":hd:R" arg; do
case $arg in
d) # Set directory, e.g. -d /foo
dir=$OPTARG
;;
R) # Optional level value, e.g. -R 123
OI=$OPTIND # Backup old value.
((OPTIND--)) # Decrease argument index, to parse -R again.
while getopts ":R:" r; do
case $r in
R)
# Check if value is in numeric format.
if [[ $OPTARG =~ ^[0-9]+$ ]]; then
level=$OPTARG
else
level=1
fi
;;
:)
# Missing -R value.
level=1
;;
esac
done
[ -z "$level" ] && level=1 # If value not found, set to 1.
OPTIND=$OI # Restore old value.
;;
\? | h | *) # Display help.
echo "$0 usage:" && grep " .)\ #" $0
exit 0
;;
esac
done
echo Dir: $dir
echo Level: $level
Here are few tests for scenarios which works:
$ ./getopts.sh -h
./getopts.sh usage:
d) # Set directory, e.g. -d /foo
R) # Optional level value, e.g. -R 123
\? | h | *) # Display help.
$ ./getopts.sh -d /foo
Dir: /foo
Level:
$ ./getopts.sh -d /foo -R
Dir: /foo
Level: 1
$ ./getopts.sh -d /foo -R 123
Dir: /foo
Level: 123
$ ./getopts.sh -d /foo -R wtf
Dir: /foo
Level: 1
$ ./getopts.sh -R -d /foo
Dir: /foo
Level: 1
Scenarios which doesn't work (so the code needs a bit of more tweaks):
$ ./getopts.sh -R 123 -d /foo
Dir:
Level: 123
More information about getopts
usage can be found in man bash
.
See also: Small getopts tutorial at Bash Hackers Wiki
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
Try:
while getopts "hd:R:" arg; do
case $arg in
h)
echo "usage"
;;
d)
dir=$OPTARG
;;
R)
if [[ $OPTARG =~ ^[0-9]+$ ]];then
level=$OPTARG
elif [[ $OPTARG =~ ^-. ]];then
level=1
let OPTIND=$OPTIND-1
else
level=1
fi
;;
\?)
echo "WRONG" >&2
;;
esac
done
I think the above code will work for your purposes while still using getopts
. I've added the following three lines to your code when getopts
encounters -R
:
elif [[ $OPTARG =~ ^-. ]];then
level=1
let OPTIND=$OPTIND-1
If -R
is encountered and the first argument looks like another getopts parameter, level is set to the default value of 1
, and then the $OPTIND
variable is reduced by one. The next time getopts
goes to grab an argument, it will grab the correct argument instead of skipping it.
Here is similar example based on the code from Jan Schampera's comment at this tutorial:
#!/bin/bash
while getopts :abc: opt; do
case $opt in
a)
echo "option a"
;;
b)
echo "option b"
;;
c)
echo "option c"
if [[ $OPTARG = -* ]]; then
((OPTIND--))
continue
fi
echo "(c) argument $OPTARG"
;;
\?)
echo "WTF!"
exit 1
;;
esac
done
When you discover that OPTARG von -c is something beginning with a hyphen, then reset OPTIND and re-run getopts (continue the while loop). Oh, of course, this isn't perfect and needs some more robustness. It's just an example.
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