bash-hackers-wiki/docs/howto/getopts_tutorial.md

374 lines
12 KiB
Markdown
Raw Permalink Normal View History

2024-04-02 21:19:20 +02:00
---
tags:
- bash
- shell
- scripting
- arguments
- positional
- parameters
- options
- getopt
- getopts
---
2023-07-05 11:10:03 +02:00
2024-04-02 21:19:20 +02:00
# Small getopts tutorial
2023-07-05 11:10:03 +02:00
## Description
**Note that** `getopts` is neither able to parse GNU-style long options
(`--myoption`) nor XF86-style long options (`-myoption`). So, when you
want to parse command line arguments in a professional ;-) way,
`getopts` may or may not work for you. Unlike its older brother `getopt`
(note the missing *s*!), it's a shell builtin command. The advantages
2023-07-05 11:10:03 +02:00
are:
- No need to pass the positional parameters through to an external
program.
- Being a builtin, `getopts` can set shell variables to use for
parsing (impossible for an *external* process!)
- There's no need to argue with several `getopt` implementations
which had buggy concepts in the past (whitespace, ...)
2023-07-05 11:10:03 +02:00
- `getopts` is defined in POSIX(r).
------------------------------------------------------------------------
Some other methods to parse positional parameters - using neither
**getopt** nor **getopts** - are described in: [How to handle positional
parameters](../scripting/posparams.md).
2023-07-05 11:10:03 +02:00
### Terminology
It's useful to know what we're talking about here, so let's see...
2023-07-05 11:10:03 +02:00
Consider the following command line:
mybackup -x -f /etc/mybackup.conf -r ./foo.txt ./bar.txt
These are all positional parameters, but they can be divided into
several logical groups:
- `-x` is an **option** (aka **flag** or **switch**). It consists of a
dash (`-`) followed by **one** character.
- `-f` is also an option, but this option has an associated **option
argument** (an argument to the option `-f`): `/etc/mybackup.conf`.
The option argument is usually the argument following the option
2024-03-30 20:09:26 +01:00
itself, but that isn't mandatory. Joining the option and option
2023-07-05 11:10:03 +02:00
argument into a single argument `-f/etc/mybackup.conf` is valid.
2024-03-30 20:09:26 +01:00
- `-r` depends on the configuration. In this example, `-r` doesn't
take arguments so it's a standalone option like `-x`.
2023-07-05 11:10:03 +02:00
- `./foo.txt` and `./bar.txt` are remaining arguments without any
associated options. These are often used as **mass-arguments**. For
example, the filenames specified for `cp(1)`, or arguments that
2024-03-30 20:09:26 +01:00
don't need an option to be recognized because of the intended
2023-07-05 11:10:03 +02:00
behavior of the program. POSIX(r) calls them **operands**.
To give you an idea about why `getopts` is useful, The above command
line is equivalent to:
mybackup -xrf /etc/mybackup.conf ./foo.txt ./bar.txt
which is complex to parse without the help of `getopts`.
The option flags can be **upper- and lowercase** characters, or
**digits**. It may recognize other characters, but that's not
2023-07-05 11:10:03 +02:00
recommended (usability and maybe problems with special characters).
### How it works
In general you need to call `getopts` several times. Each time it will
use the next positional parameter and a possible argument, if parsable,
and provide it to you. `getopts` will not change the set of positional
parameters. If you want to shift them, it must be done manually:
shift $((OPTIND-1))
# now do something with $@
Since `getopts` sets an exit status of *FALSE* when there's nothing
left to parse, it's easy to use in a while-loop:
2023-07-05 11:10:03 +02:00
while getopts ...; do
...
done
`getopts` will parse options and their possible arguments. It will stop
2024-03-30 20:09:26 +01:00
parsing on the first non-option argument (a string that doesn't begin
with a hyphen (`-`) that isn't an argument for any option in front of
2023-07-05 11:10:03 +02:00
it). It will also stop parsing when it sees the `--` (double-hyphen),
which means [end of options](../dict/end_of_options.md).
2023-07-05 11:10:03 +02:00
### Used variables
|variable|description|
|--------|-----------|
|[OPTIND](../syntax/shellvars.md#OPTIND)|Holds the index to the next argument to be processed. This is how `getopts` "remembers" its own status between invocations. Also useful to shift the positional parameters after processing with `getopts`. `OPTIND` is initially set to 1, and **needs to be re-set to 1 if you want to parse anything again with getopts**
|[OPTARG](../syntax/shellvars.md#OPTARG)|This variable is set to any argument for an option found by `getopts`. It also contains the option flag of an unknown option.|
|[OPTERR](../syntax/shellvars.md#OPTERR)|(Values 0 or 1) Indicates if Bash should display error messages generated by the `getopts` builtin. The value is initialized to **1** on every shell startup - so be sure to always set it to **0** if you don't want to see annoying messages! **`OPTERR` is not specified by POSIX for the `getopts` builtin utility --- only for the C `getopt()` function in `unistd.h` (`opterr`).** `OPTERR` is bash-specific and not supported by shells such as ksh93, mksh, zsh, or dash.|
2023-07-05 11:10:03 +02:00
`getopts` also uses these variables for error reporting (they're set to
2023-07-05 11:10:03 +02:00
value-combinations which arent possible in normal operation).
### Specify what you want
The base-syntax for `getopts` is:
getopts OPTSTRING VARNAME [ARGS...]
where:
|Option|Description|
|------|-----------|
|`OPTSTRING`|tells `getopts` which options to expect and where to expect arguments (see below)|
|`VARNAME`|tells `getopts` which shell-variable to use for option reporting|
|`ARGS`|tells `getopts` to parse these optional words instead of the positional parameters|
2023-07-05 11:10:03 +02:00
#### The option-string
The option-string tells `getopts` which options to expect and which of
them must have an argument. The syntax is very simple --- every option
2023-07-05 11:10:03 +02:00
character is simply named as is, this example-string would tell
`getopts` to look for `-f`, `-A` and `-x`:
getopts fAx VARNAME
When you want `getopts` to expect an argument for an option, just place
a `:` (colon) after the proper option flag. If you want `-A` to expect
an argument (i.e. to become `-A SOMETHING`) just do:
getopts fA:x VARNAME
If the **very first character** of the option-string is a `:` (colon),
which would normally be nonsense because there's no option letter
preceding it, `getopts` switches to "**silent error reporting mode**".
2023-07-05 11:10:03 +02:00
In productive scripts, this is usually what you want because it allows
you to handle errors yourself without being disturbed by annoying
messages.
#### Custom arguments to parse
The `getopts` utility parses the [positional
parameters](../scripting/posparams.md) of the current shell or function by
2023-07-05 11:10:03 +02:00
default (which means it parses `"$@"`).
You can give your own set of arguments to the utility to parse. Whenever
additional arguments are given after the `VARNAME` parameter, `getopts`
2024-03-30 20:09:26 +01:00
doesn't try to parse the positional parameters, but these given words.
2023-07-05 11:10:03 +02:00
This way, you are able to parse any option set you like, here for
example from an array:
while getopts :f:h opt "${MY_OWN_SET[@]}"; do
...
done
A call to `getopts` **without** these additional arguments is
**equivalent** to explicitly calling it with `"$@"`:
getopts ... "$@"
### Error Reporting
Regarding error-reporting, there are two modes `getopts` can run in:
- verbose mode
- silent mode
For productive scripts I recommend to use the silent mode, since
2024-03-30 20:09:26 +01:00
everything looks more professional, when you don't see annoying
standard messages. Also it's easier to handle, since the failure cases
2023-07-05 11:10:03 +02:00
are indicated in an easier way.
#### Verbose Mode
|failure|message|
|-------|-------|
|invalid option|`VARNAME` is set to `?` (question-mark) and `OPTARG` is unset|
|required argument not found|`VARNAME` is set to `?` (question-mark), `OPTARG` is unset and an *error message is printed*|
2023-07-05 11:10:03 +02:00
#### Silent Mode
|failure|message|
|-------|-------|
|invalid option|`VARNAME` is set to `?` (question-mark) and `OPTARG` is set to the (invalid) option character|
|required argument not found|`VARNAME` is set to `:` (colon) and `OPTARG` contains the option-character in question|
2023-07-05 11:10:03 +02:00
## Using it
### A first example
Enough said - action!
Let's play with a very simple case: only one option (`-a`) expected,
2023-07-05 11:10:03 +02:00
without any arguments. Also we disable the *verbose error handling* by
preceding the whole option string with a colon (`:`):
``` bash
#!/bin/bash
while getopts ":a" opt; do
case $opt in
a)
echo "-a was triggered!" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
esac
done
```
I put that into a file named `go_test.sh`, which is the name you'll see
2023-07-05 11:10:03 +02:00
below in the examples.
Let's do some tests:
2023-07-05 11:10:03 +02:00
#### Calling it without any arguments
$ ./go_test.sh
$
2023-07-05 11:10:03 +02:00
2024-03-30 20:09:26 +01:00
Nothing happened? Right. `getopts` didn't see any valid or invalid
options (letters preceded by a dash), so it wasn't triggered.
2023-07-05 11:10:03 +02:00
#### Calling it with non-option arguments
$ ./go_test.sh /etc/passwd
$
2023-07-05 11:10:03 +02:00
Again --- nothing happened. The **very same** case: `getopts` didn't
2023-07-05 11:10:03 +02:00
see any valid or invalid options (letters preceded by a dash), so it
2024-03-30 20:09:26 +01:00
wasn't triggered.
2023-07-05 11:10:03 +02:00
The arguments given to your script are of course accessible as `$1` -
`${N}`.
#### Calling it with option-arguments
Now let's trigger `getopts`: Provide options.
2023-07-05 11:10:03 +02:00
First, an **invalid** one:
$ ./go_test.sh -b
Invalid option: -b
$
2023-07-05 11:10:03 +02:00
2024-03-30 20:09:26 +01:00
As expected, `getopts` didn't accept this option and acted like told
2023-07-05 11:10:03 +02:00
above: It placed `?` into `$opt` and the invalid option character (`b`)
into `$OPTARG`. With our `case` statement, we were able to detect this.
Now, a **valid** one (`-a`):
$ ./go_test.sh -a
-a was triggered!
$
2023-07-05 11:10:03 +02:00
You see, the detection works perfectly. The `a` was put into the
variable `$opt` for our case statement.
Of course it's possible to **mix valid and invalid** options when
2023-07-05 11:10:03 +02:00
calling:
$ ./go_test.sh -a -x -b -c
-a was triggered!
Invalid option: -x
Invalid option: -b
Invalid option: -c
$
2023-07-05 11:10:03 +02:00
Finally, it's of course possible, to give our option **multiple
2023-07-05 11:10:03 +02:00
times**:
$ ./go_test.sh -a -a -a -a
-a was triggered!
-a was triggered!
-a was triggered!
-a was triggered!
$
2023-07-05 11:10:03 +02:00
The last examples lead us to some points you may consider:
2024-03-30 20:09:26 +01:00
- **invalid options don't stop the processing**: If you want to stop
2023-07-05 11:10:03 +02:00
the script, you have to do it yourself (`exit` in the right place)
- **multiple identical options are possible**: If you want to disallow
these, you have to check manually (e.g. by setting a variable or so)
### An option with argument
Let's extend our example from above. Just a little bit:
2023-07-05 11:10:03 +02:00
- `-a` now takes an argument
- on an error, the parsing exits with `exit 1`
``` bash
#!/bin/bash
while getopts ":a:" opt; do
case $opt in
a)
echo "-a was triggered, Parameter: $OPTARG" >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
```
Let's do the very same tests we did in the last example:
2023-07-05 11:10:03 +02:00
#### Calling it without any arguments
$ ./go_test.sh
$
2023-07-05 11:10:03 +02:00
2024-03-30 20:09:26 +01:00
As above, nothing happened. It wasn't triggered.
2023-07-05 11:10:03 +02:00
#### Calling it with non-option arguments
$ ./go_test.sh /etc/passwd
$
2023-07-05 11:10:03 +02:00
2024-03-30 20:09:26 +01:00
The **very same** case: It wasn't triggered.
2023-07-05 11:10:03 +02:00
#### Calling it with option-arguments
**Invalid** option:
$ ./go_test.sh -b
Invalid option: -b
$
2023-07-05 11:10:03 +02:00
2024-03-30 20:09:26 +01:00
As expected, as above, `getopts` didn't accept this option and acted
2023-07-05 11:10:03 +02:00
like programmed.
**Valid** option, but without the mandatory **argument**:
$ ./go_test.sh -a
Option -a requires an argument.
$
2023-07-05 11:10:03 +02:00
The option was okay, but there is an argument missing.
Let's provide **the argument**:
2023-07-05 11:10:03 +02:00
$ ./go_test.sh -a /etc/passwd
-a was triggered, Parameter: /etc/passwd
$
## See also
- Internal: [posparams](../scripting/posparams.md)
- Internal: [case](../syntax/ccmd/case.md)
- Internal: [while_loop](../syntax/ccmd/while_loop.md)
2023-07-05 11:10:03 +02:00
- POSIX
[getopts(1)](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html#tag_20_54)
and
[getopt(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getopt.html)
- [parse CLI
ARGV](https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash)
- [handle command-line arguments (options) to a
script](http://mywiki.wooledge.org/BashFAQ/035)