You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
8.7 KiB

4 years ago
---
title: My bash template for option parsing
tags: [getopts, bash, template, bashisms, shell]
updated: 2021-12-29 18:23:28
4 years ago
description: a template for option parsing in Bash based on getopt
---
Hello again,
11 months ago
in the past years I have written a lot of scripts which rely on
`getopt`, a program that parses command line options (both short and long) and
arguments.
<!--more-->
Getopt is part of the [util-linux](https://www.kernel.org/pub/linux/utils/util-linux/) package.
4 years ago
Please, [don't confuse getopt with getopts](https://unix.stackexchange.com/questions/62950/getopt-getopts-or-manual-parsing-what-to-use-when-i-want-to-support-both-shor)
; they are quite different.
11 months ago
See [this](https://www.mariusvw.com/2013/02/24/bash-getopt-versus-getopts/)
and [this](http://abhipandey.com/2016/03/getopt-vs-getopts/) for a comparison
4 years ago
between the two.
11 months ago
*Please note that the script and the documentation reported here, except for
the introduction and the "Reason" paragraph, is outdated. For this
reason you should refer to the [repository](https://software.franco.net.eu.org/frnmst-archives/fbopt).*
*Please note that the documentation and source code reported here are old.
Refer to the [fbopt](https://software.franco.net.eu.org/frnmst-archives/fbopt) repository.*
## Reason
4 years ago
To avoid reinventing the wheel and confusion, I have written some notes and a
dummy script that will serve as template. The script is called `fbopt`, i.e:
*Franco Bash Option Parsing Template*. The purpose of this is to change
the current implementations of the option parsers already present in
some scripts I have written, as well as use it for new ones.
## Usage
You can for example adapt and include the template as a separate part of your
program by using `source ./fbopt` or `. ./fbopt` from the main
script.
## Bashisms
[Bashisms](https://mywiki.wooledge.org/Bashism) are Bash specific syntax
elements. You will find at least the following bashisms in the template
| Bashism | Example |
|---------|---------|
| local variables | `local variable='value'` |
| arrays | `"${array[@]}"` |
| indirect variables | `"${!variable}"` |
## Conventions
These elements should be common sense and not be specific to `fbopt`
| Convention | Example | Notes |
|------------|---------|-------|
| constants are enclosed within single quotes | `'constant'` | elements within single quotes (only) are not interpreted by the shell |
| variables enclosed within double quotes | `"${variable}"` | double quotes serve as a delimiter between multiple variable names if these are consecutive. Every variable between the quotes is interpolated |
| variables use the curly braces notation | `"${variable}"` | curly braces serve as a delimiter between multiple variable names if these are consecutive |
| variables not enclosed within double quotes | `${?}` or `for v in ${values}; do echo "${v}"; done` | the only variables allowed without double quotes are integers (such as return values) and loop iterators (because these won't work otherwise) |
4 years ago
## Dependencies and compliancy
The template is known to work with the following packages
| Package | Version command |
|---------|-----------------|
| [GNU Bash](http://www.gnu.org/software/bash/bash.html) | `$ bash --version` |
| [getopt](https://www.kernel.org/pub/linux/utils/util-linux/) | `$ getopt --version` |
`fbopt` compatibility:
| Package | Package version | fbopt version |
|---------|-----------------|---------------|
| GNU Bash | `GNU bash, version 4.4.23(1)-release (x86_64-unknown-linux-gnu)` | `0.1`, `0.2`, `0.3` |
| getopt | `getopt from util-linux 2.33` | `0.1`, `0.2`, `0.3` |
4 years ago
## The template
To allow inclusion in any project the template is released under the [CC0 1.0 license](https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt).
What follows is `fbopt` version `0.3`.
4 years ago
```shell
#!/bin/bash
#
# fbopt version 0.3
4 years ago
#
# Written in 2018 by Franco Masotti/frnmst <franco.masotti@student.unife.it>
#
11 months ago
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the public
4 years ago
# domain worldwide. This software is distributed without any warranty.
#
11 months ago
# You should have received a copy of the CC0 Public Domain Dedication along
# with this software. If not, see
# <http://creativecommons.org/publicdomain/zero/1.0/>.
4 years ago
#
#
# See also https://blog.franco.net.eu.org/notes/my-bash-option-parsing-template.html
4 years ago
#
show_help()
{
cat <<-EOF
Usage: <program_name> [OPTION] ARGUMENT
4 years ago
<Description>
Mandatory arguments to long options are mandatory for short options too.
4 years ago
Options:
-a, --flag-a=FLAGA set a
-b, --flag-b=FLAGB set b
4 years ago
-c enable c
-d enable d
-e, --flag-e=FLAGE set e
-f[=FLAGF] enable and or set f
4 years ago
with an optional argument
-h, --help print this help
--print-flags print the enabled options. This can also
be used to print the default options
4 years ago
Exit status:
0 if OK,
1 if an error occurred.
<License header>
<Copyright>
EOF
}
# A function that prints the variable name and value of all
# the flags enabled by the user. This is useful to check that
# all the flags are correct, as kind of a dry run.
show_flags()
{
local flags="${*}"
for flag in ${flags}; do
printf "%s='%s'\n" "${flag}" "${!flag}"
done
}
getopt_error()
{
local program_name="${0}"
printf "%s\n" "Try '"${program_name}" --help' for more information"
} 1>&2 2>&-
main()
{
# Create a new array from the reference of the input one.
# See https://stackoverflow.com/questions/1063347/passing-arrays-as-parameters-in-bash
# See some comments below.
declare -a argc=("${!1}")
# Set the options. flag_f has an optional argument.
local getopt_short_options='a:b:c:def::hi'
local getopt_long_options='flag-a:,flag-b:,flag-e,print-flags,help,'
4 years ago
# Set the default values for the flags.
local flag_a=''
local flag_b=''
local flag_c=''
local flag_d='false'
local flag_e='false'
local flag_f='false'
4 years ago
local program_name="${0}"
opts="$(getopt \
--name "${program_name}" \
--shell bash \
--options "${getopt_short_options}" \
--longoptions "${getopt_long_options}" \
-- \
"${argc[@]}")"
getopt_retval=${?}
# Check that getopt works and that some kind of argument
# is passed to the script. This is "quotation hell".
a="'"${argc[@]}"'"
{ [ ${getopt_retval} -ne 0 ] || [ -z "${a}" ]; } && getopt_error && return 1
eval set -- "${opts}"
# Option parsing.
while [ "${1}" != '--' ]; do
case "${1}" in
-a | --flag-a ) flag_a="${2}";
shift 1 ;;
--flag-b ) flag_b="${2}";
shift 1 ;;
-c ) flag_c="${2}";
shift 1 ;;
-d ) flag_d='true' ;;
-e | --flag-e ) flag_e='true' ;;
-f ) flag_f="${2}";
shift 1 ;;
-h | --help ) help='true' ;;
--print-flags ) print_flags='true' ;;
esac
# Iterate through all arguments.
shift 1
done
shift 1
# Everything else after '--' is an argument.
argc="${*}"
# Check that the flags that must be non empty are actually not empty.
# A user might infact circumvent getopt's mechanisms like this
# ./program -flag ''
# This can also be done inside the option parser loop but to avoid nestings
# I prefer it done here.
{ [ -z "${flag_a}" ] \
|| [ -z "${flag_b}" ]; } \
&& getopt_error && return 1
4 years ago
[ "${print_flags}" = 'true' ] \
&& show_flags \
'flag_a' \
'flag_b' \
'flag_c' \
'flag_d' \
'flag_e' \
'flag_f' \
&& return 0
[ "${help}" = 'true' ] && show_help && return 0
# Override values of optional parameters.
[ -z "${flag_f}" ] && flag_f='true'
4 years ago
# From now on you should call a function or an external program
# using the values of the flag variables.
[ "${flag_a}" = 'alpha' ] && do_something_alpha && return ${?}
[ "${flag_a}" = 'a' ] && do_something_a && return ${?}
}
# Test dependencies and versions.
# getopt must return 4 to be fully compatible. See getopt's manual.
which bash getopt 1>/dev/null 2>/dev/null && { getopt -T; [ ${?} -eq 4 ]; }
4 years ago
# Get and pass argc to the main function.
# All this work with an array must be done to preserve
# quotations for arguments that have whitespaces.
# See https://lists.debian.org/debian-user/2007/12/msg01244.html
declare -a opts=()
for opt in "${@}"; do
opts=("${opts[@]}" "${opt}")
done
main 'opts[@]'
```
~
Enjoy :)