3 ms·
The one downside of this is that it doesn't handle squeezing flags as in foo -da bar whereas getopts does. On the other hand, with (the Bash built-in) get
by bewuethr 6y ago
The one downside of this is that it doesn't handle squeezing flags as in
foo -da bar
whereas getopts does. On the other hand, with (the Bash built-in) getopts you're limited to single character flags.
- Spivak 6y agoYou can do that it will just make things a little less pretty. while (( $# )); do case "$1" in -*h*|--help) do_help exit ;; -*v*|--version) do_version exit ;; -*d*|--debug) debug=true ;;& -*a*|--arg) value="$2" shift ;;& esac shift done It doesn't support args of the form -avalue but those a pretty uncommon anyway.
- jolmg 6y agoThat wouldn't work in the general case. Those patterns would also match long options. If I add a case pattern `--all)`, and I call the script with `--all`, it's also going to match -*a*|--arg) You could fix that with: -a*|-[!-]*a*|--arg) > It doesn't support args of the form -avalue but those a pretty uncommon anyway. You could -a*|-[!-]*a*|--arg) if [[ "$1" != --arg ]]; then value="${1#*a}" fi if [[ ! "$value" ]]; then value="$2" shift fi ;;& Putting the option stuck together to its value has the advantage of working nicely with brace expansion. For example, you can call `strace -p{1111,2222,3333}` to trace those 3 pids and avoid having to type `-p` 3 times.
- jolmg 6y agoAs a final addendum, case clauses of options that take arguments like -a/--arg should not be terminated with `;;&`, but rather with `;;`.
- Spivak 6y agoThis is awesome! Thank you for being a total bash nerd.
- jolmg 6y agoThere's still one problem. To exemplify it, if you call with `-av`, it'll process the `v` as the option `-v` instead of the option value to `-a`. If you only have one possible option that takes a value, this can be fixed by putting its case clause before all others. If you have more, then that'll require things to get a little more complicated: -d*|-[!-]*d*|--debug) if [[ ! "$finished_case" && ("$1" = --debug || "$1" =~ '^[^ad]*d') ]]; then debug=true fi ;;& -a*|-[!-]*a*|--arg) if [[ ! "$finished_case" && ("$1" = --arg || "$1" =~ '^[^a]*a') ]]; then if [[ "$1" != --arg ]]; then value="${1#*a}" fi if [[ ! "$value" ]]; then value="$2" shift fi finished_case=true fi ;;& ... esac shift finished_case= done All case-clauses would need to use `;;&` by the way, including `-v` and `-h`. The regex is generally: "^[^${all_options_with_values}${current_option}]*${current_option}" Another problem is that option and argument non-recognition would not work as previously layed out. You can include short options that aren't recognized, and they'll be ignored instead of raising errors. For positional arguments, one would need a condition to check for options, since using `;;&` for everything means that everything would land to *) Maybe those are the last issues, but this is already out of hand for otherwise small and simple shell scripts. All these complications arise from trying to support the sticking together of short options and their possible values. Processing arguments in a case loop is much, much simpler if we avoid supporting those 2 features.