Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Define a convention for parsing command-line flags (#10)
Resolves #3 Looking at the shell scripts in our repos, we seem to parse command-line flags in basically four different ways. This PR compares these methods and picks a convention based on our preference. ## 1. Using `getopts` ### Pros: - Uses bash built-in [`getopts`](https://man7.org/linux/man-pages/man1/getopts.1p.html) command - Can chain short flag names (e.g. `script.sh -abcd`) - Built-in error handling - Code is compact - Currently, the majority of our shell scripts parse command-line flags in this way ### Cons: - Only supports short flag names - Need to learn the options syntax (e.g. `getopts 'ht:f'`) ### Example: ```bash TARGET_FILE='' FORCE='false' # Parse command-line flags. # One colon indicates that the previous flag expects a required value, while # two colons to indicate an optional value. while getopts 'ht:f' opt; do case "${opt}" in h) echo 'Help is on its way' exit ;; t) TARGET_FILE="${OPTARG}" ;; f) FORCE='true' ;; *) >&2 echo 'Sorry, invalid option' exit 1 esac done readonly TARGET_FILE readonly FORCE ``` ## 2. Using `getopt` ### Pros: - Supports both short and long flag names - Can specify flag values using `=` (e.g. `script.sh --target-file=/tmp/file`) - Built-in error handling ### Cons: - Uses GNU based [`getopt`](https://linux.die.net/man/1/getopt) command (i.e., not available on macOS) - Need to learn the options syntax - Performs unexpected flag matching For example, `script.sh -target /tmp/file` results in `TARGET_FILE=arget`. This isn't an issue in the other parsing methods. ### Example: ```bash # Parse command-line flags. # One colon indicates that the previous flag expects a required value, while # two colons to indicate an optional value. OPTIONS="$(getopt \ --options 'ht:f' \ --longoptions 'help,target-file:,force' \ -- \ "$@")" # Process command-line flags. eval set -- "${OPTIONS}" TARGET_FILE='' FORCE='false' while true; do case "$1" in -h|--help) echo 'Help is on its way' exit ;; -t|--target-file) TARGET_FILE="$2" shift # For flag name. shift # For flag value. ;; -f|--force) FORCE='true' shift # For flag name. ;; --) shift break ;; *) >&2 echo 'Sorry, invalid option' exit 1 esac done readonly TARGET_FILE readonly FORCE ``` ## 3. Using a custom implementation for long flags ### Pros: - Code is easier to read and write than `getopt` / `getopts` ### Cons: - Completely custom implementation - No built-in error handling - Can't specify flag values using `=` ### Example: ```bash TARGET_FILE='' FORCE='false' while [[ "$#" -gt 0 ]]; do case "$1" in --help) echo 'Help is on its way' exit ;; --target-file) TARGET_FILE="$2" shift # For flag name. shift # For flag value. ;; --force) FORCE='true' shift # For flag name. ;; *) >&2 echo 'Sorry, invalid option' exit 1 esac done readonly TARGET_FILE readonly FORCE ``` ## 4. Using a custom implementation for short and long flags ### Pros: - Code is easier to read and write than `getopt` / `getopts` - Requires little extra work to support both long and short flag names ### Cons: - Completely custom implementation - No built-in error handling - Can't specify flag values using `=` - Can't chain short flag names ### Example: ```bash TARGET_FILE='' FORCE='false' while [[ "$#" -gt 0 ]]; do case "$1" in -h|--help) echo 'Help is on its way' exit ;; -t|--target-file) TARGET_FILE="$2" shift # For flag name. shift # For flag value. ;; -f|--force) FORCE='true' shift # For flag name. ;; *) >&2 echo 'Sorry, invalid option' exit 1 esac done readonly TARGET_FILE readonly FORCE ``` --- Source material * https://stackoverflow.com/a/402410/3769045 * https://stackoverflow.com/a/34531699/3769045 * https://stackoverflow.com/a/7069755/3769045 <a data-ca-tag href="https://codeapprove.com/pr/tiny-pilot/style-guides/10"><img src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review on CodeApprove" /></a>
- Loading branch information