Hacker News new | ask | show | jobs
by Spivak 2167 days ago
But handling args isn't that bad in bash.

    while [[ $# -gt 0 ]]; do
      case "$1" in
         -h|--help)
           do_help
           exit
           ;;
         -v|--version)
           do_version
           exit
           ;;
         -d|--debug)
           debug=true
           shift
           ;;
         -a|--arg)
           arg_value=$2
           shift 2
           ;;
      esac
    done
2 comments

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('-v','--version',action='version', version='demo',help='Print version information')
    parser.add_argument('-d','--debug', help='Enable Debug Mode')
    parser.add_argument('a','arg', help="Argument Documentation")
    args = parser.parse_args()
Personally I feel like this is more readable code, gets me better validation, and help docs for "free". That's the attraction.
Elegant, but then it's no longer a basic shell-script as it requires python installed.

If you can live with additional dependencies, then I like the node [1] commander package, which is very readable and nice to work with in my opinion.

        #!/usr/bin/env node
        const { program } = require('commander');

        program
          .command('clone <source> [destination]')
          .description('clone a repository')
          .action((source, destination) => {
            console.log('clone command called');
          });
It also automatically generates the --help output for ./script -h

[1] https://github.com/tj/commander.js/

To expand on that pattern:

    while (( $# )); do
      case "$1" in
        -h|--help)
          usage
          exit
          ;;

        -v|--version)
          do_version
          exit
          ;;

        -d|--debug)
          debug=true
          ;;

        -a|--arg)
          arg_value="$2"
          shift
          ;;

        *)
          if [[ ! -v pos1 ]]; then
            pos1="$1"
          elif [[ ! -v pos2 ]]; then
            pos2="$1"
          else
            >&2 printf "%s: unrecognized argument\n" "$1"
            >&2 usage
            exit 1
          fi
      esac

      shift
    done
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.
You 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.
That 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.
As a final addendum, case clauses of options that take arguments like -a/--arg should not be terminated with `;;&`, but rather with `;;`.
This is awesome! Thank you for being a total bash nerd.