Live data from Hacker News

Help Message for Shell Scripts

samizdat.dev

121–129 of 129 posts

Re: Help Message for Shell Scripts

#121
post #7

Handling of arguments is one of the reasons I reach for Python or Powershell instead of a bash script when writing my own stuff. https://docs.python.org/3/library/argparse.html is great. Powershell has the Param keyword that functions like argparse in Python https://docs.microsoft.com/en-us/powershell/module/microsoft...

PowerShell also has comment-based help, which is like a manpage embedded as a comment within the script. It's like OP's suggested help format, but better.

https://docs.microsoft.com/en-us/powershell/module/microsoft...

Re: Help Message for Shell Scripts

#123
post #120

Earlier quoted context omitted.

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.

Re: Help Message for Shell Scripts

#124

Even better: Don’t use bash. I started using Python instead of bash. It’s way better to read and more maintainable. If I need the performance of native-Unix commands, I can still use them using subprocess.

I've been migrating shell scripts to python and have found the 'sh' library invaluable for pulling in pure CLI commands from the bash script and adding to the python implementation. http://amoffat.github.io/sh/ It's essentially an abstraction layer above subprocess. Quick example - to use ifconfig natively in python:

from sh import ifconfig print(ifconfig("wlan0"))

Re: Help Message for Shell Scripts

#125
post #123
post #120

Earlier quoted context omitted.

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" ]]; t…

As a final addendum, case clauses of options that take arguments like -a/--arg should not be terminated with `;;&`, but rather with `;;`.

Re: Help Message for Shell Scripts

#126
post #14

Earlier quoted context omitted.

Thumbs up for 'Click'. I used it for a project once, and I was really happy with it. Easy to use, good docs. Would use it again.

Googling the library appears to be about ~8,000 lines of code (core.py is ~2,000 alone). Is that really reasonable sounding to most people for parsing CLI input/output and display manpages or helptext?

I didn’t mean to suggest we should reach for Click for simple help/manpage display.

The case I used it for was much more complex. What I liked about it was the easy to use API, clear documentation & examples, and readable patterns.

For simple text display, I like the solution from the article, and I learned something new about bash scripts. Also, I learned from comments you can use heredoc in bash!

Re: Help Message for Shell Scripts

#127
post #125
post #123

Earlier quoted context omitted.

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" ]]; t…

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.

Re: Help Message for Shell Scripts

#128
post #127
post #125

Earlier quoted context omitted.

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.

There'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.

Re: Help Message for Shell Scripts

#129
post #68

Unrelated: Is there any connection between the author and the other sam[]zdat who writes about society and other intriguing topics? https://samzdat.com/

I wouldn't know, but there is no reason for me to be thinking something like that. "Samizdat" is not really a name or something, it's a transliteration of "самиздат", which is a short/colloquial for "самостоятельное издательство", which literally means "self-publishing" (this was a thing during the USSR, where "self-publishing" was basically opposed to "real, official government-approved publishing"). I believe it's…

Ah, thanks for the explanation.
Post reply on HN