Argbash – Bash Argument Parsing Code Generator

  • Handling command-line arguments in Bash is easy. Bash's `getopts` handles short and long arguments gnu-style without any problem, out of the box, without any need for libraries or complicated packages.

    This pattern handles lots of styles of options: short and long options (-h, --help), `--` for separating options from positional args, with GNU-style long options (--output-file=$filename).

      while getopts :o:h-: option
      do case $option in
             h ) print_help;;
             o ) output_file=$OPTARG;;
             - ) case $OPTARG in
                     help ) print_help;;
                     output-file=* ) output_file=${OPTARG##*=};;
                     * ) echo "bad option $OPTARG" >&2; exit 1;;
                 esac;;
             '?' ) echo "unknown option: $OPTARG" >&2; exit 1;;
             : ) echo "option missing argument: $OPTARG" >&2; exit 1;;
             * ) echo "bad state in getopts" >&2; exit 1;;
         esac
      done
      shift $((OPTIND-1))
      (( $# > 0 )) && printf 'remaining arg: %s\n' "$@"

  • I've found that every time my bash scripts become sufficiently complex, I end up rewriting them in Python.

  • I'm a fan of BashBoilerPlate (Bash3BoilerPlate) - https://github.com/xwmx/bash-boilerplate

    It uses a similar style of deriving the arguments from the usage declaration, but it also includes some useful logging functions and is all in one script. There's some more info available on their style choices here: https://bash3boilerplate.sh/

  • I really wish bash could evolve...in particular around control flow, variable assignment, string interpolation, and arithmetic.

    I love working with bash, but it has some footguns that really require an expert hand.

    I know bash as-is will always be around for backwards compatibility with the god-knows-how-many scripts out there. It'd just be nice if there were a widely embraced path forward that kept the shell scripting spirit while shedding some of the unintuitive behaviors

  • I've always assumed that there was some argument parser available that just sets things as environment variables, and that my google-fu is just too weak to find it.

    Why ccouldn't I just go `source argbash _ARG_ --single option o --bool print --position positional -- $@` and get _ARG_OPTION, _ARG_PRINT, and _ARG_POSITIONAL environment variables set based on the commands passed in, without having to dump a hundred lines of code in my script?

  • Am I the only one who finds argbash unnecessarily complicated?

    For my own needs, I rely on a tiny function I wrote, called process_optargs. Example use:

      source /path/to/process_optargs
      source /path/to/error
      source /path/to/is_in
    
      function myfunction () {   # example function whose options/arguments we'd want to process
    
      # Define local variables which will be populated or checked against inside process_optargs
        local -A OPTIONS=()
        local -a ARGS=()
        local -a VALID_FLAG_OPTIONS=( -h/--help -v --version )    # note -v and --version represent separate flags here! (e.g. '-v' could be for 'verbose')
        local -a VALID_KEYVAL_OPTIONS=( -r/--repetitions )
        local COMMAND_NAME="myfunction"
    
      # Process options and arguments; exit if an error occurs
        process_optargs "$@" || exit 1
    
      # Validate and collect parsed options and arguments as desired
        if   is_in '-h' "${!OPTIONS[@]}" || is_in '--help' "${!OPTIONS[@]}"
        then display_help
        fi
    
        if   is_in '-r'            "${!OPTIONS[@]}"; then REPS="${OPTIONS[-r]}"
        elif is_in '--repetitions' "${!OPTIONS[@]}"; then REPS="${OPTIONS[--repetitions]}"
        fi
    
        if   test "${#ARGS[@]}" -lt 2
        then error "myfunction requires at least 2 non-option arguments"
             exit 1
        fi
    
        # ...etc
      }
    
    It works as you'd expect, with appropriate checks for correctness of inputs, and is compatible with most unix conventions (including '--' and '-' as arguments).

    If anyone's interested let me know and I can share the code.

  • as cool as this is, i feel that anything with the complexity of more than one or two arguments should really be written in a different language, like Python (or AppleScript for macOS users). bash just isn’t the right tool for the job then.

  • GPT is pretty good at figuring out argument parsing code based on example CLI usage. Like this:

        > Write bash code to parse the following arguments:
        >
        > $ ./export.sh --out-file dump.json --db foo.sqlite --include-headers --exclude-rev -vvv -p9000
    
        Here's an example bash code to parse the arguments:
    
        #!/bin/bash
    
        # Default values for optional arguments
        INCLUDE_HEADERS=false
        EXCLUDE_REV=false
        VERBOSE_LEVEL=0
        PORT=0
    
        # Parse the arguments
        while [[ "$#" -gt 0 ]]; do
            case $1 in
            --out-file) OUT_FILE="$2"; shift ;;
            --db) DB="$2"; shift ;;
            --include-headers) INCLUDE_HEADERS=true ;;
            --exclude-rev) EXCLUDE_REV=true ;;
            -v) VERBOSE_LEVEL=$((VERBOSE_LEVEL+1)) ;;
            -p) PORT="$2"; shift ;;
            -h|--help) 
                echo "Usage: ./export.sh [OPTIONS]"
                echo ""
                echo "Options:"
                echo "--out-file     Output file path (required)"
                echo "--db           Database path (required)"
                echo "--include-headers  Include column headers in output (default: false)"
                echo "--exclude-rev   Exclude revisions in output (default: false)"
                echo "-v              Increase verbosity level (default: 0)"
                echo "-p              Port number (default: 0)"
                echo "-h, --help      Show help"
                exit 0
                ;;
            *) echo "Unknown parameter passed: $1"; exit 1 ;;
            esac
            shift
        done
    
        # Check required arguments
        if [[ -z $OUT_FILE ]]; then
            echo "Error: missing argument --out-file"
            exit 1
        fi
    
        if [[ -z $DB ]]; then
            echo "Error: missing argument --db"
            exit 1
        fi
    
        # Print parsed arguments
        echo "OUT_FILE=$OUT_FILE"
        echo "DB=$DB"
        echo "INCLUDE_HEADERS=$INCLUDE_HEADERS"
        echo "EXCLUDE_REV=$EXCLUDE_REV"
        echo "VERBOSE_LEVEL=$VERBOSE_LEVEL"
        echo "PORT=$PORT"

  • Languages that I work with infrequently enough to remember how to use them - like Bash - are the absolute perfect place to apply LLM tech like ChatGPT.

    Prompt:

    > Write a bash script "foo.sh" that accepts a required filename, optional flags for "-r/--reverse" and "-s/--skip" and an optional "-o/--output=other-file" parameter. It should have "-h/--help" text too explaining this.

    Then copy and paste out the result and write the rest of the script (or use further prompts to get ChatGPT to write it for you).

    Could it be done better if I spent more time on it or was a Bash expert? Absolutely, but for most of the times when I need to do something like this I really don't care too much about the finished quality.

  • Cute and lots of effort went into this, but code generation is, unfortunately, unmaintainable and inflexible. This seems targeted at users who want to avoid mastery of their tools, which is fine for some.

    util-linux getopt exists.

  • This is very similar to Bashly (https://bashly.dannyb.co/) but with a lot more weird magic going on.

  • If anyone is looking for a snippet to handle both positional args along with required and optional flags (both with short and long form formats) along with basic validation I put together: https://nickjanetakis.com/blog/parse-command-line-positional..., it includes the source code annotated with comments and a demo video.

  • I have great pleasure when using docopt in Python.

    I see docopts is ‘the same’ implementation but for shell, have never tried it though.

    ==== docopt helps you:

    - define the interface for your command-line app, and - automatically generate a parser for it. ====

    http://docopt.org/

    https://github.com/docopt/docopts

  • Yet another point where Fish is a delight.

    Going to Python/whatever is not quite the same — shell scripts are not as much written as extracted from shell history, so switching to a separate language is a large extra step.

  • I have used are – for many projects and it is wonderful. But as others have indicated, be mindful of whether or not Bash is the right tool for the task at hand.

  • What's with:

        # [ <-- needed because of Argbash
    
    There's also this bit:

    The square brackets in your script have to match (i.e. every opening square bracket [ has to be followed at some point by a closing square bracket ]).

    There is a workaround — if you need constructs s.a. red=$'\e[0;91m', you can put the matching square bracket behind a comment, i.e. red=$'\e[0;91m' # match square bracket: ].

    That kind of kludginess is a turn-off.

  • Huh, it uses M4 to build its DSL, how quaint.

    I wish there were more projects on the other side of the spectrum: take the script's self-reported usage string, à la docopt [0], and derive argument-parsing code from that. After all, we have GPT-4 now.

    [0] https://github.com/docopt/docopts

  • [dead]