Bash code generator for command-line arguments
github.com
github.com
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/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: $@" echo "message" 1>&2
Yes, that's probably a good idea, though I cannot recall this causing a problem for any script that I've written so far. fancycommand --help | less
works as expected. Otherwise, the help text will dump to the screen and less will erase it when it decides to redraw its empty buffer! Actual error messages should go to stderr so that I can redirect them to an error log or so I can suppress them if need be, or I could also be doing > /dev/stdout to silence normal output but I don't want errors to be discarded. $ tens.sh --verbose 5
10
100
1000
10000
100000
Calculated 5 results
$ tens.sh --verbose 5 > results
Calculated 5 results(Since you use it both for -h and for unknown options, perhaps make it not exit at all, and make the caller exit with an appropriate code?)
Additionally, you should output the actual executable name (as passed in on $0) rather than hardcoding something. So maybe at the top say 'executable=$0; shift'; replace parseflags.sh with $executable; and replace $1 with $0 in your option parse loop.
I tried using $0 years ago, but I didn't like seeing the full path name (or the "./parseflags.sh" string), or maybe it was because I often use shell alias, so the $0 becomes the alias name, or something like that. So now, I just replace the string "parseflags.sh" with the name of the script for each script. I think I tried using $(basename $0) at one point, but didn't like that either, though I don't remember why.
I hope that the template is simple enough that people can customize it as they wish.
Edit: Fix typo
And you can just use $0 everywhere so there's technically no reason to pull it out into a $executable variable, but tbh that gets a bit confusing in functions.
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.
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.
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.
Do you have any recommendations for an IDE to use for Bash scripting?
Pycharm has it built in or as a plugin now too.
For a scripting language, better look elsewhere.
If we had to standardize on one language, perhaps Python would be a good choice.
I drive zsh but still use bash scripts a lot cause "portability" and "it's available everywhere" but that argument just doesn't hold up when I'm already in control of most of my stacks and have docker access on pretty much every host I'm in.
I'm forcing myself to use Xonsh more, which despite having all its own quirks, is so much less mental overhead going between Python development. Yeah maybe it's a few more steps (than zero for bash) to provision, but it's worth it.
command -ffilename -f filename --file=filename -- -f is not an option
My template is at https://gist.github.com/webb/ff380b0eee96dafe1c20c2a136d85ef....
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.
In the end, it was easier to hammer onto bash. Also, there are great linters for bash scripts, so things work out.
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.
Starting to get into Xonsh. Still a learning curve, cause it's neither bash nor python, but it's really refreshing.
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.
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.
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.
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™.
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.
That's a clever way to go.
Sometimes the other way around works too: taking the most fiddly manipulations (say, date or time intervals) and re-homing them to a small Python script, but leaving the bash intact.
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.
[0]: https://dave.autonoma.ca/blog/2019/06/16/typesetting-markdow...
[1]: https://github.com/DaveJarvis/keenwrite/blob/master/installe...
[2]: https://github.com/DaveJarvis/keenwrite/blob/master/build-te...
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/opt...
That would then be used like:
eval "$(parseArgs \
--opt foo \
--opt-bool bar \
--pos balls \
)"`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!
By who? Why?
This is the version that I’ve always used:
https://manpages.debian.org/buster/util-linux/getopt.1.en.ht...