├── petopts.bash ├── petopts.sh ├── LICENSE ├── example.sh └── README.md /petopts.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | readonly options=(a b: h v) 4 | readonly usage="[-ahv] [-b param] [operand ...]" 5 | readonly version="petops example version 6.9" 6 | 7 | die() { warn "${@:2}"; exit "$1"; } 8 | warn() { printf '%s: %s\n' "${0##*/}" "$*" >&2; } 9 | usage() { printf 'usage: %s %s\n' "${0##*/}" "$usage"; exit; } 10 | version() { printf '%s\n' "$version"; exit; } 11 | 12 | typeset -A arg opt 13 | while getopts ":${options[*]}" o; do case $o in 14 | :) die 1 "option requires an argument -- $OPTARG" ;; 15 | \?) die 1 "unknown option -- $OPTARG" ;; 16 | *) ((opt[$o]++)); arg[$o]=$OPTARG ;; 17 | esac; done; shift "$((OPTIND - 1))" 18 | -------------------------------------------------------------------------------- /petopts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | options="a b: h v" 4 | usage="[-ahv] [-b param] [operand ...]" 5 | version="petops example version 6.9" 6 | 7 | die() { rc=$1; shift; warn "$@"; exit "$rc"; } 8 | warn() { printf '%s: %s\n' "${0##*/}" "$*" >&2; } 9 | usage() { printf 'usage: %s %s\n' "${0##*/}" "$usage"; exit; } 10 | version() { printf '%s\n' "$version"; exit; } 11 | 12 | for o in $options; do unset "opt_${o%:}"; done 13 | 14 | while getopts ":$options" o; do case $o in 15 | :) die 1 "option requires an argument -- $OPTARG" ;; 16 | \?) die 1 "unknown option -- $OPTARG" ;; 17 | *) eval "opt_$o=\$((opt_$o + 1)) arg_$o=\$OPTARG" ;; 18 | esac; done; shift "$((OPTIND - 1))" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example cat implementation using petopts.sh option parsing. 4 | 5 | options="h n v w:" 6 | usage="[-hnv] [-w width] [file ...]" 7 | version="cat version 1.0" 8 | 9 | main() { 10 | [ "$opt_h" ] && usage 11 | [ "$opt_v" ] && version 12 | [ "$opt_w" ] && 13 | case $arg_w in *[!0-9]*) 14 | die 1 "invalid width: $arg_w" 15 | esac 16 | 17 | # Both options enable line numbers. 18 | if [ "$opt_n" ] || [ "$opt_w" ]; then 19 | withln=true 20 | else 21 | withln=false 22 | fi 23 | 24 | # Read from stdin if no files are specified. 25 | [ $# = 0 ] && set /dev/stdin 26 | 27 | for file do 28 | [ "$file" = - ] && 29 | file=/dev/stdin 30 | 31 | ! [ -e "$file" ] && 32 | die 2 "$file: No such file or directory" 33 | 34 | ! [ -r "$file" ] && 35 | die 3 "$file: Permission denied" 36 | 37 | ln=1 38 | 39 | while IFS= read -r line || [ "$line" ]; do 40 | if "$withln"; then 41 | printf '%*s\t%s\n' "${arg_w:-6}" \ 42 | "$ln" "$line" 43 | ln=$((ln+1)) 44 | else 45 | printf '%s\n' "$line" 46 | fi 47 | done <"$file" 48 | done 49 | } 50 | 51 | die() { rc=$1; shift; warn "$@"; exit "$rc"; } 52 | warn() { printf '%s: %s\n' "${0##*/}" "$*" >&2; } 53 | usage() { printf 'usage: %s %s\n' "${0##*/}" "$usage"; exit; } 54 | version() { printf '%s\n' "$version"; exit; } 55 | 56 | for o in $options; do unset "opt_${o%:}"; done 57 | 58 | while getopts ":$options" o; do case $o in 59 | :) die 1 "option requires an argument -- $OPTARG" ;; 60 | \?) die 1 "unknown option -- $OPTARG" ;; 61 | *) eval "opt_$o=\$((opt_$o + 1)) arg_$o=\$OPTARG" ;; 62 | esac; done; shift "$((OPTIND - 1))" 63 | 64 | main "$@" 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | petopts 2 | ======================================================================== 3 | 4 | Portable and simple option parsing, powered by getopts(1p). 5 | 6 | > pet /pɛt/ To stroke in a gentle or loving manner. 7 | 8 | Unlike most (bash) option parser frameworks, where the complexity can 9 | easily dwarf that of the actual script, petopts is purposefully made as 10 | simple and concise as possible. 11 | 12 | 13 | Features 14 | ------------------------------------------------------------------------ 15 | 16 | * Easy to use 17 | * Minimal/simple 18 | * Portable 19 | * No GNU style long options 20 | * No optional parameters 21 | * Count of option occurrences 22 | * Short usage message 23 | * Support `--` argument to indicates the end of the options 24 | * Proper separation of option parsing and handling 25 | * POSIX utility conventions compliance 26 | 27 | 28 | Usage 29 | ------------------------------------------------------------------------ 30 | 31 | ### tl;dr 32 | 33 | 1. Copy the content of [petopts.sh](petopts.sh) to the top of a script 34 | 2. Change `options`, `usage` and `version` 35 | 3. Handle the parsed options and remaining operands 36 | 37 | Given `options="a b: c"`, petopts would set `opt_a`, `opt_b` and `opt_c` 38 | to the amount each of the options are supplied (empty if 0). `arg_b` 39 | would contain the option argument of the -b option. 40 | 41 | 42 | ### Details 43 | 44 | [petopts.sh](petopts.sh) is supposed to be used at the top of a script. 45 | The following 3 variables have to be adapted: 46 | 47 | `options`: A string containing the space separated list of alphanumeric 48 | option characters. If a character is followed by a \, the option 49 | shall be expected to have an argument. 50 | 51 | `usage`: The string printed by the `usage()` function. 52 | 53 | `version`: The string printed by the `version()` function. 54 | 55 | For each option in the `options` variable, petopts will set 56 | corresponding variables in the form of `opt_*` and `arg_*`, where `*` is 57 | the option character. The `opt_*` variables will hold the number of 58 | occurrences of the options (unset if an option does not occur), while 59 | the `arg_*` variables store the option arguments (empty if an option has 60 | no option argument). 61 | 62 | After parsing the options, the positional parameters are shifted until 63 | only operands remain. 64 | 65 | For example, to conditionally do something with the option 'a', 'h' and 66 | 'v', we could do: 67 | 68 | ```sh 69 | [ "$opt_h" ] && usage 70 | [ "$opt_v" ] && version 71 | [ "$opt_a" ] && printf 'Option a present with argument: %s\n' "$arg_a" 72 | ``` 73 | 74 | petopts provides the following functions: 75 | 76 | `die()`: Prints its arguments using `warn()` and exits the script with 77 | the exit status given by the first argument. 78 | 79 | `warn()`: Prints its arguments to stderr, prefixed by the program name. 80 | 81 | `usage()`: Prints the content of the `usage` variable and exits the 82 | script with status 0. 83 | 84 | `version()`: Prints the content of the `version` variable and exits the 85 | script with status 0. 86 | 87 | For a complete script using petopts, check [example.sh](example.sh). 88 | 89 | #### Bash version 90 | 91 | Instead of using `opt_*` and `arg_*`, the bash version sets the 92 | associative arrays `opt` and `arg`, using the option characters as keys. 93 | 94 | 95 | Usage of eval 96 | ------------------------------------------------------------------------ 97 | 98 | `eval` is considered dangerous because it can execute "dirty" data and 99 | thus allow arbitrary code execution. However, how petopts uses `eval`, 100 | to dynamically set the option variables, is totally safe. 101 | 102 | 103 | Limitations 104 | ------------------------------------------------------------------------ 105 | 106 | * No GNU style long options 107 | * No optional parameters 108 | --------------------------------------------------------------------------------