getopts
stops parsing the argument list as soon as it reaches a non-option argument. That is, it returns success as it iterates left-to-right over the option arguments, then returns failure on the first non-option. So the proper use case is put options and their arguments on the command line before any non-option arguments
.
The getops
tutorials then show a shift $((OPTIND - 1))
command. The while
loop iterates through the option arguments, then the shift
removes them from the positional variables, leaving the non-option arguments (if any) in $@
.
In code, using variables like those in tutorials:
while getopts ':s:l:u:' OPTION
do
case "${OPTION}" in
s) special=${OPTARG};;
l) lower=${OPTARG};;
u) upper=${OPTARG};;
\?) echo "$0: Error: Invalid option: -${OPTARG}" >&2; exit 1;;
:) echo "$0: Error: option -${OPTARG} requires an argument" >&2; exit 1;;
esac
done
shift $((OPTIND - 1))
# now the positional variables have the non-option arguments
echo "${1}"
With your example command (./randompassword.sh -s2 -u2 -l3 16
), the output is 16
.
I added a leading :
to the getopts string because it gives you "silent error reporting", which lets your script catch errors and issue a friendlier complaint. That's the function of the extra two options I added to the case
statement.
If the user broke the rule and typed option arguments after non-option arguments, the trailing option argument(s) would not be parsed in the while
loop above. It/they would appear in the $@
array holding the remaining positional arguments. If the user followed the guidelines and typed all the option arguments before the non-option arguments, the $@
array will have the non-option arguments after the while
loop and shift
commands are done.
Per your concern about the order in which non-option arguments are typed, either your script expects these in a strict order, which your user must follow (else they get errors), or your script doesn't require a particular order to them, and must use other parsing methods to figure out how to use them.
But in my view, un-ordered arguments are why we have option letters. The remaining arguments are ordered, else they would have option letters. That's just my opinion, though.
ls foo -l
would take-l
as a filename. The GNU tools relax that requirement (to the apparent ire of some) and you can get that behaviour in a shell script with the util-linux version of thegetopt
tool (without the s), but the standard shell builtingetopts
(with s) doesn't support it in any shell I know of. So, in a shell script it's easier if you can accept having to give the non-option length last. – ilkkachu Sep 10 '22 at 07:26getopt
: https://unix.stackexchange.com/a/663806/170373. Also note that the "traditional"getopt
versions can't handle arguments with whitespace cleanly (e.g. filenames likemy little poem.txt
), so they can be rather problematic. (but that's not a problem here when you're just passing numbers.) – ilkkachu Sep 10 '22 at 07:32