2

for instance, gcc accepts the input file without any flag and the output file with the -o flag in:

gcc input.c -o output.out

or

gcc -o output.out input.c

I am creating a random password generator bash script, the user should be able to specify the number of special chars, lowercase chars and uppercase chars using the -s, -l, -u flags respectively. They should also be able to specify the length of the password without any flag.

example usage:

./randompassword.sh -s2 -u2 -l3 16

should mean a password with 2 special, 2 uppercase, 3 lowercase chars and a length of 16.

I am able to parse the flags using getopts like so:

while getopts s:l:u: flag
do
    case "${flag}" in
        s) special=${OPTARG};;
        l) lower=${OPTARG};;
        u) upper=${OPTARG};;
    esac
done

However I can't figure out how to get the length argument which does not have a flag.

I tried using $1 but this argument should be able to be at any position in the command just like the gcc example.

Shriram
  • 216
  • 2
  • 6
  • 2
    Note that the standard POSIX behaviour is that options are taken only at the beginning, not after any non-option arguments. So e.g. 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 the getopt tool (without the s), but the standard shell builtin getopts (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:26
  • See e.g. this for the use of the "advanced" getopt: https://unix.stackexchange.com/a/663806/170373. Also note that the "traditional" getopt versions can't handle arguments with whitespace cleanly (e.g. filenames like my 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

1 Answers1

5

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.

Sotto Voce
  • 4,131
  • 1
    Correction: getopts doesn't move non-option arguments to the end, or rearrange the arguments in any other way. To use getopts, options must be given before non-option arguments on the command line. So if you run the script with ./randompassword.sh -s2 16 -u2 -l3, getopts will only process the -s2 option, and the -u3 and -l3 arguments will be treated as non-option arguments. – Gordon Davisson Sep 10 '22 at 23:57
  • @GordonDavisson thanks for your correction. I've rewritten my answer to fix the misinformation (which came from poor testing I performed a few years ago). – Sotto Voce Sep 11 '22 at 02:45
  • Looks good now! – Gordon Davisson Sep 11 '22 at 04:23