9 ms·
Bash code generator for command-line arguments
- michael-ax 6y agowhat a gem!
- usrme 6y agoQuite an impressive project all things considered; definitely something to keep in mind for the next project that inevitably starts out as a simple Bash script. Out of curiosity: if you (the reader) get to a point where you need more complicated and esoteric functionality that Bash can provide, do you still keep hammering at it or turn to a more capable (and readable) language? Just wondering if people are usually working in very constrained environments where other languages aren't able to be readily used.
- lysium 6y agoI've tried that path with little success. Usually, arg parsing makes me try some other language (perl, python) but other things got complicated there, mostly running other processes, capturing their exit status, pipe-ing their output etc. In the end, it was easier to hammer onto bash. Also, there are great linters for bash scripts, so things work out.
- IggleSniggle 6y agotcl is designed for this situation
- lysium 6y agoThx, I’ll give it a shot!
- lou1306 6y ago> but other things got complicated there, mostly running other processes, capturing their exit status, pipe-ing their output Note that Python's subprocess module [0] has received many updates since v3.5, and now I find it relatively painless. Most of the times, subprocess.check_output() or subprocess.run() will do the trick. [0]: https://docs.python.org/3/library/subprocess.html https://docs.python.org/3/library/subprocess.html
- lysium 6y agoThx, I’ll check it out again!
- kortex 6y agoI've been in a similar boat, yet despite as my experience with it grows, bash just wears on me. Everything feels like a hack. Sure, piping is easy, but if statements are atrocious. Starting to get into Xonsh. Still a learning curve, cause it's neither bash nor python, but it's really refreshing.
- xurukefi 6y agoAs soon as as the cli becomes non-trivial I switch to python and use docopt for that (really cool module btw.). Pipelining processes is not as idiomatic in Python as it is in Bash though, but everything is better than getopts. I'd rather decapitate myself with a teaspoon than using getopts.
- OJFord 6y agoDocopt exists (now, think it was originally python) for many languages including bash [0] (or perhaps it's POSIX, haven't checked). I like it too, not least because I can use ~the same thing in multiple languages and not have to remind myself how the arg parser for language X works each time. [0] - https://github.com/docopt/docopts https://github.com/docopt/docopts
- vlovich123 6y agoYeah, I’m kind of shocked to see so many masochists in this thread. Built-in subprocess is nice and all but I’ve had a lot of joy using https://amoffat.github.io/sh/ https://amoffat.github.io/sh/ to do the work (and the maintainer is super responsive). I sometimes don’t have the flexibility to have external dependencies but I’d use it everywhere if Python just folded it into the default library.
- raziel2p 6y agoIt's a trade-off. Especially if the script contains a lot of pipes and redirection, it's difficult to reproduce with the same elegance in something like Python. My biggest successes have been extracting these parts into very small stand-alone shell scripts which the Python application then invokes, which gives you the best of both worlds. The best example off the top of my head is `mysqldump | sed ... | gzip` with decent error handling (i.e. set -o pipefail in bash). My environment isn't all that constrained, but I do have to deal with old Python versions (though at least I don't have to support 2.6 any more...), and a surprising amount of the features that would make replacing bash easier (subprocess.call especially) are only available fairly recently.
- vlovich123 6y agoI’m curious about your experience that pipes and redirects are harder with python. Do you mean slightly more verbose or just complicated and difficult? I’m curious if you’ve tried the sh Python module [1]. In some ways it’s much prettier to read although the initial writing of it can be awkward to get used to at first. [1] https://amoffat.github.io/sh/ https://amoffat.github.io/sh/
- raziel2p 6y agoI mean significantly more verbose and more complicated. Fee free to try it yourself - replicate `bash -eo pipefail -c 'mysqldump example_db | gzip > dump.sql.gz'` in Python with streaming (databases can be several GB, so you can't read it all in memory). I have not tried any of these libraries, though they look nice. The only times I've had to write scripts that make heavy use of subprocesses and pipes, I want them to work with only the standard library so I can just rsync them and they just work™.
- vlovich123 6y agoYou have to dig through the documentation a bit on the website but all the info is there[1] from sh import mysqldump from sh import gzip gzip(mysqldump("example_db", _piped=True), _out="dump.sql.gz") or if you don't want the magic import thing: import sh sh.gzip(sh.mysqldump("example_db", _piped=True), _out="dump.sql.gz") It's definitely foreign to shell scripting languages since the piping syntax is a bit different & you have to remember to do `_piped=True` since it's not parallel by default. But the default behavior is pipefail & exit on the command failing IIRC so it's more of a choose your poison thing (do you want a subtle perf issue or a subtle bug in your script not handling error states correctly). And I find it easier to read. + if you want to customize anything about gzip or not rely on needing the binary in the path, then you can just switch it to Python-native gzip pretty easily. That's my favorite feature. Conciseness if I'm just translating a script with progressive complexity options to migrate things that need more complexity or different requirements within the same script without having to rewrite it from scratch. [1] https://amoffat.github.io/sh/ https://amoffat.github.io/sh/
- techdragon 6y agoSpeaking as someone who frequently injects the most ridiculous of things into a makefile in order to bootstrap build tools and such things... and as someone who had learned my way through enough of the Chrome OS build tool chain to customise a fork of it for building my own variant of CoreOS back in the early days before Kubernetes won the container platform wars... It depends. Sometimes you use these tools because they are already responsible for so much stuff it makes sense to just extend it no matter how crazy it is (Chrome Os build tool was built as hundreds of lines of shell script which built on top of portage which is even more hundred of lines of shell script) ... sometimes it’s because that’s the tool you know will be available (the dozens of bootstrap scripts I’ve beaten crushed and smashed into makefiles complete with OS detection and other stuff) ... and sometimes you don’t have any read you just do it because it’s what your comfortable thinking in. I’ve definitely written bash scripts that would have been better in Python but at the time my mind was building the script up as a sequence of Unix tool operations and pipelines not as a Python program.
- arthur2e5 6y ago> getopt is discouraged, getopts doesn't support long options Hold up. The only version of getopt with long option support is from util-linux, which fixes all the whitespace issues people were discouraging getopt for. So what's the stated goal of this project again? Heck, you don't even need to write code to detect usage of legacy getopt. It will panic every time you use the new stuff once it sees the option to turn on escaping. Advertise all your cool features like argument types and help generation, but not this. I don't see how writing m4 is easier than just a while-case loop, but I may try it for the extra features.
- sjburt 6y agoGetopt isn't a builtin and the BSD/OSX one is different.
- gregn610 6y agoAwesome! I think it would be even better if it could generate from doctopt style definitions :p
- teddyh 6y ago> getopt is discouraged By who? Why? This is the version that I’ve always used: https://manpages.debian.org/buster/util-linux/getopt.1.en.html https://manpages.debian.org/buster/util-linux/getopt.1.en.ht...
- holtalanm 6y agoI'm probably going to get downvoted for this, but: writing bash scripts in general just isn't a fun experience. years ago, i started just writing ruby scripts for execution in the terminal instead of bash, and haven't had any problems with it so far.
- jaydecus 6y agoA few years ago I would have disagreed, but today I quite enjoy it. What changes my opinion was mostly having a decent IDE configured to deal with the annoying bash syntax errors, and a quicker way to prototype. Though in generqal I always thought Bash scripting to be fun, due to the wide array of programs available that you can directly invoke in your scripts script. The feeling of gluing programs together with pipes into a larger abstraction is just amazing, and bash does that very elegantly.
- Aaronstotle 6y agoThat last part is why I routinely stick with bash for quick scripts. For example, I usually create boolean values to check for a proper config by using grep or grep -c. Do you have any recommendations for an IDE to use for Bash scripting?
- kortex 6y agoShellcheck (https://www.shellcheck.net/ https://www.shellcheck.net/) is essential, with or without IDE. Pycharm has it built in or as a plugin now too.
- amelius 6y agoYes, Bash is nice as long as you use it as a shell only. For a scripting language, better look elsewhere. If we had to standardize on one language, perhaps Python would be a good choice.
- jonahx 6y agoRuby is definitely a better tool for this but there are situations where it's not available but bash is. That portability is really the only advantage.
- aidenn0 6y agoThis is incredibly full featured. I wrote a much less full featured and less ergonomic library that targets sh rather than bash. It was 70 lines of shell including whitespace. Here's an example of its usage[1]. Each option calls a function, optionally with an argument. There is also sugar for options that merely toggle a value. Non flag parameters call a function "positional parameter" to avoid a dependence on arrays. It does not generate usage or man pages. 1: https://github.com/jasom/the-copper-searcher/blob/master/options.sh https://github.com/jasom/the-copper-searcher/blob/master/opt...
- bxparks 6y agoI start all my bash scripts from the following template, then modify it as needed. It is not fancy, but I think it supports the lowest common denominator that is compatible with other flag processing systems in all other languages that I am aware of. (In particular, it does not support '--option=opt' but uses '--option opt', which is cross-compatible). I like that it is small, easy to maintain, and avoids external dependencies in my shell script: Edit: Send error messages to stderr Edit2: People seem to like it. I created a GitHub gist for it here: https://gist.github.com/bxparks/e67a3d6fc6b5d62b51304b3d9de25405 https://gist.github.com/bxparks/e67a3d6fc6b5d62b51304b3d9de2... #!/bin/bash set -eu function usage() { echo "Usage: parseflags.sh [--help|-h] [--binary] [--option opt] [--] files..." 1>&2 exit 1 } binary=0 option='' while [[ $# -gt 0 ]]; do case $1 in --binary) binary=1 ;; --option) shift; option="$1" ;; --help|-h) usage ;; --) shift; break ;; -*) echo "Unknown flag '$1'" 1>&2; usage ;; *) break ;; esac shift done echo "binary=$binary" echo "option=$option" echo "files: $@"
- skinner927 6y agoIn my experience this is the simplest way. The only mod I’d suggest is to put positional arguments into an array so you can put flags anywhere but this will work fine for 99% of scripts.
- bxparks 6y agoThe problem I have with bash arrays is that I use them so infrequently that I cannot remember its syntax. I have to look up the manual each time, and it's difficult to find things in the bash man pages.
- matheusmoreira 6y agoThis is nice! I suggest writing messages to standard error instead of standard output.
- bxparks 6y ago
- isenthil 6y agoI would download and give it a try.
- databasher 6y agoLike @bxparks, I have a template for my bash scripts command-line parsing, but I like to stick to GNU-style options and I find that Bash's getopts command handles all these cases without trouble: command -ffilename -f filename --file=filename -- -f is not an option My template is at https://gist.github.com/webb/ff380b0eee96dafe1c20c2a136d85ef0 https://gist.github.com/webb/ff380b0eee96dafe1c20c2a136d85ef....
- LukeShu 6y agoUsing `-:` to build long-options on top of the short-option-only `getopts` builtin is a great hack.
- databasher 6y agoThanks!
- buu700 6y agoArgbash is great. I found the output unnecessarily verbose for the simple functionality that I wanted, so the way I've used it has been to put the following in a script/function and then eval the output: https://gist.github.com/buu700/72d0461d318bfe8c11e36d2316882b2a https://gist.github.com/buu700/72d0461d318bfe8c11e36d2316882... That would then be used like: eval "$(parseArgs \ --opt foo \ --opt-bool bar \ --pos balls \ )"`
- chubot 6y agoYeah this problem with shell is real ... it ironically is not good at writing command line interfaces! If anyone is interested in Oil, I would appreciate help either: (1) Running this library with OSH. Try it and tell me what breaks! OSH runs many unmodified bash scripts. (2) Help with a new builtin that will address the deficiencies of getopt/getopts. You could help design it! https://github.com/oilshell/oil/issues/469 https://github.com/oilshell/oil/issues/469
- thangalin 6y agoThere's a bit of code duplication[0] that often emerges as a result of parsing command-line arguments in shell scripts. The duplication can be eliminated by defining[1] the set of arguments to pass into a reusable template script[2]. [0]: https://dave.autonoma.ca/blog/2019/06/16/typesetting-markdown-part-3/#eliminate-code-duplication https://dave.autonoma.ca/blog/2019/06/16/typesetting-markdow... [1]: https://github.com/DaveJarvis/keenwrite/blob/master/installer#L34 https://github.com/DaveJarvis/keenwrite/blob/master/installe... [2]: https://github.com/DaveJarvis/keenwrite/blob/master/build-template https://github.com/DaveJarvis/keenwrite/blob/master/build-te...