9 ms·
Argbash – Bash Argument Parsing Code Generator
- sacnoradhq 4y agoCute 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.
- simonw 4y agoLanguages 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.
- stefantalpalaru 4y ago[dead]
- tpoacher 4y agoAm 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.
- feisuzhu 4y agoI've found that every time my bash scripts become sufficiently complex, I end up rewriting them in Python.
- m463 4y agothat's exactly what I do, and argparse (I believe the motivation for argbash) helps a lot
- Cheeeetah 4y agoI mainly use Python to handle JSON, while use Shell for other things.
- deleted 4y ago[deleted]
- captnswing 4y agothis ^
- ff317 4y agoYeah, I figure if a bash script goes over ~10-20 lines, or involves really long lines, or quotes within quotes within quotes of different kinds, it's time to move on to Python or similar.
- 3np 4y agoIt'\''s always the quotes.
- vaughan 4y agoTypeScript is now tenable for bash scripts with the fast-starting Bun runtime https://bun.sh/ https://bun.sh/. Before Bun, Node+V8 was just too slow to start. IMHO all scripts should be written in TypeScript...you get typechecking and all the rest of the editing experience. Plus things like Wallaby.js for live coding/testing. My `.bashrc` now just runs a TypeScript script with Bun. Allows you to use proper structure instead of brittle spaghetti stuff. The number of times I'm debugging silly PATH stuff was too much...
- softskunk 4y agoas 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.
- meindnoch 4y ago>or AppleScript for macOS users You must be joking.
- user3939382 4y agoAppleScript: It’s easy because you write it like natural English! Sort of. We’ll it’s not really natural English. It also doesn’t have semantics that make sense for a programming language. Good luck!
- xp84 4y agoHey but don't worry, it has great documentation, in the form of question posts from 2006 on "MacOSXHints" that may or may not have been answered! Also don't worry, you can write AppleScript in JavaScript syntax too! Too bad 100% of the code samples out there to help you are in the "other" syntax :D
- user3939382 4y agoI never had another interpreter that I had to learn by manually fuzzing it. AppleScript makes bash look like F#.
- Joker_vD 4y agoHuh, 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 https://github.com/docopt/docopts
- rickydroll 4y agoI 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.
- chrsig 4y agoI 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
- kps 4y agoRegarding the post topic, it's a shame bash hasn't embraced ksh93 getopts. This is enough to provide short and long options including help messages: usage=' [-1] [+NAME?cmd - XXX Synopsis here] [s:sample?XXX Sample option with argument]:[MODE] [v:verbose?XXX Show progress] file ...' while getopts "$usage" _ do typeset "opt_$_"="${OPTARG:-1}";; done shift OPTIND-1
- johnchristopher 4y agoI gave up on bash (more precisely on bashisms. edit: and because of bashims) and moved to dash or whatever is at /bin/sh. If it can't be done with ash/dash then I look into Python or node or PHP or what fits the situation best.
- skowalak 4y agoI did the same. I believe the biggest selling point of shell over other scripting languages is its availability on so many platforms. Unfortunately bashisms have ruined the effort of writing a one-size-fits-all script. So I, too, moved to POSIX shell syntax for the small problems and another shell scripting language for the bigger problems.
- dwheeler 4y agoBash is very widely available. If you use bashisms like arrays, just use bash as the first line.
- deleted 4y ago[deleted]
- ndsipa_pomu 4y agoI'm a fan of BashBoilerPlate (Bash3BoilerPlate) - https://github.com/xwmx/bash-boilerplate 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/ https://bash3boilerplate.sh/
- gdevenyi 4y agoI work with both, argbash's parser and boilerplate's helper functions.
- tveyben 4y agoI 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/ http://docopt.org/ https://github.com/docopt/docopts https://github.com/docopt/docopts
- caymanjim 4y agoWhat'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.
- gdevenyi 4y agoThis is because argbash is actually an m4 preprocessor, and the brackets are part of the language
- shaftway 4y agoI'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?
- akho 4y agoYet 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.
- iamjackg 4y agoThis is very similar to Bashly (https://bashly.dannyb.co/ https://bashly.dannyb.co/) but with a lot more weird magic going on.
- databasher 4y agoHandling 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' "$@"
- sacnoradhq 4y agoI don't know where you learned shell scripting, but your formatting is very confusing and nonstandard. - while...; do / if ...; then - space before ;; - no space before case match - use util-linux getopt because getopts doesn't handle long args - DRY with a die() function rather than exits littered all over
- fsckboy 4y ago>I don't know where you learned shell scripting, but your formatting is very confusing i don't know where you learned English composition, but I can't follow your critique. are you pointing out your preferences (which may well match gnu or bsd standards, i don't know tell me) or things that actually make a difference? Did he break emacs auto-indent? Are you pointing out all the flaws or the correct ways, i'm having to eyeball diff. Use more of your words. while do doesn't seem substantially worse/confusing compared to while;do unless you have some reason? spacing out ;; does it make a difference? or do you like things spaced out? etc.
- sacnoradhq 4y agoFlagged for not being a professional response to constructive feedback.
- nadaviv 4y agoGPT 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"
- Izkata 4y agoThis is actually very close to what I write manually; I've never been a fan of getopts syntax since you need to do the loop and case anyway.
- version_five 4y agoHonestly nobody cares. It's insulting to every reader to post this kind of crap.
- 4y ago
- nickjj 4y agoIf 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-arguments-and-flags-with-bash https://nickjanetakis.com/blog/parse-command-line-positional..., it includes the source code annotated with comments and a demo video.