Personally I have to say that I started writing more bash scripts again recently, I even wrote one that interfaces with the GPIO ports on a Raspberry Pi. Works like a charm, is concise and (I find) also quite readable: https://gist.github.com/adewes/7a4c20a5a7379e19d78ba54521d3d...
But then again I'm also a person who enjoys Perl, so maybe it is time for me to be banished to a lonely island :D
Jinja2 can produce HTML pages, but it was never exclusively designed for that. In fact one of the earliest uses of Jinja2 was generating LaTeX.
Bash is far from perfect, but it's readily available at many people's fingertips, and it gets you to some solutions really fast. This is cool.
If you have two type systems and you don't want them to interact, gluing them together with stringly typed bash is a good way to keep their scope small. As a user interacting with multiple programs by different authors, that's a very common use case.
Giving your type systems boundaries is a good idea. The alternative tends towards one-type-system-to-rule-them-all, an idea that Russell thoroughly explored in the early 1900's, Godel proved to be unmaintainable in the 30's, and that everybody except the Java and C# crowd has since abandoned as a dead end.
Supposedly it supports Linux, but I haven't actually tried.
I haven't yet taken the time to play with it but I'm hoping www.oilshell.org addresses some of the warts.
I do think there are a few issues that make bash non-robust for this kind of task. If you only look at this through the lens of the "type system", you have a good point about gluing views together, but I think "just strings" is not enough typing. I think I and the industry have had better luck with a base system more like JSON, instead of "just strings". It provides enough structure so that there's a straightforward way to turn wire protocols or other external data into your Java objects or whatever, let dynamic scripting languages handle it in their native base types (which often map pretty well to what JSON offers, while bash really doesn't--note that the author here isn') and are fairly reasonable to manage on their own without special tooling.
However, that aside, even if "just strings" were fine for everything, the fact that bash is designed as an interactive shell with barewords for strings really exposes a lot of gotchas for handling data from different sources, just from the sometimes-diabolical problems with quoting and I/O. In the absence of actual function return values, capturing output and storing a process exit code, which might sometimes be the last process's exit or maybe something inside the pipeline if you've set the right global options, is error-prone. It's not robust to hope that the only output coming (on two different output streams) constitutes a complete and correctly-anticipated return value from a subprogram. Errors associated with empty variable values are frequent for this reason.
Secondly, bash lacks a lot of features which you need external programs to provide, and the dependency management glue between them is not good. You'll need find, ls, sed, awk (and you may not remember the days with multiple significant Unix platforms and differences in behavior between gawk, nawk, awk-of-different-flavors), cut, sort, ps (/usr/bin/ps or /usr/ucb/ps) all stored in different platform- and environment-specific locations and exhibiting different behaviors without a clear way of constraining your dependencies or even asserting them correctly. In the modern world, you can add to that list curl, jq, parallel, and other shell tools. Fairly common tasks like YAML handling is even more of a clusterf. The fact that these tools were all designed to work for their own reasons and not as extensions to a common language isn't their* problem, but it becomes yours if you're trying to deliver a robust and serious piece of software with it. You'll do a lot of DIY around discovering their edge cases and handling them.
There are a number of other problems but I think they're lesser in scale, like the lack of a standard way to deliver and use extensions. It's hard to do safe file access. Bats is a pretty good testing framework but no one (including the author of this script) seems to use it. But what I don't really see are many advantages over a regular scripting language, for something like this. The fact that "bash is already installed" is only an advantage as long as your script isn't going to do anything interesting; once it starts doing interesting things, it needs to worry about all kinds of other things being installed.
That said, I do think it's a little unfair to tear the author apart over something that really shouldn't be any more ambitious than a cron job doing "git clone https://config-repo/config.git && config/manage.sh". The fact that they call it an "alternative to Ansible" is a little eyebrow-raising, but I'm sure it is: to them. The question isn't really whether it does what Ansible does, but if you can manage a machine's configuration with a shell script, and of course the answer is yes. But the answer to the question, "Is bash a fine language to write a general-purpose configuration management system in?": the answer is no.
That said, features such as word-splitting are terrible footguns that only exist because of the burden of backwards compatibility. Hackernews, what other languages do you suggest as Bash replacements that doesn't have as many opportunities to shoot yourself on the foot?
This is an area that I never found something I truly liked. A lot of people suggest Python for this kind of task but when I have a small script that is mostly doing pipelines and command substitutions with "$()" then the python version with subprocess is much more verbose.
... I guess pure bash might be better.
ansible all -m shell -a "hostname" -b
ansible all -m script -a "script=/home/user/myscript.sh" -b
I love to throw together a quick inventory list and run adhoc ansible commands against a bunch of servers.
while [[ "$#" -gt 0 ]]; do
Spot checking code for basic bash mistakes like this tends to tell me the quality of the code overall, as these are basic mistakes. $0.02https://www.gnu.org/software/bash/manual/html_node/Double-Qu...
X="08"
if [[ "$X" -eq 8 ]]; then
echo Y
fi
Use `bash -x` to run it in debug mode: $ bash -x test.sh
+ X=08
+ [[ 08 -eq 8 ]]
test.sh: line 2: [[: 08: value too great for base (error token is "08")
This comes into play if you're dealing with things like IPv4 address, where it is technically allowable to have leading zeros, however by convention it's never done because of code traps which see those leading zeros as octal mode, not decimal.Plus, left and single operands, in bash conditions, don't require quoting, so a lean style is actually not to use them at all.
That's false:
x='foo bar'; [ $x = 'foo bar' ]; echo $?
-bash: [: too many arguments
x='foo bar'; [ "$x" = 'foo bar' ]; echo $?
0I do a non-trivial amount of shell scripting, and there is only one warning I disable in shellcheck on a general basis (SC2016).
The author of this script clearly doesn't like quoting where the underlying value is guaranteed not to include special characters (or space), although this is somewhat inconsistent with the fact that he's using quotes in several patterns where it's not needed (eg. left or single operands in bash conditionals, or in variables initialization).
On can consider always quoting a defensive practice. In a fragile language like Bash, I personally think defensive practices are very important, so the first impression I get from a Bash program not adopting them is "not great".
But even if I wasn't experienced, quit this gatekeeping crap. Let people use the tools that work for them. It does not in any way reflect on how "serious/real" they are.
I didn't mean to discredit you for using iCloud - if it came across that way, I apologize. My original comment was meant to be tongue in cheek. I've got to admit that I'm a bit gob-smacked by your reaction... After all, my comment was similar in structure to yours. I even quote unquoted the terms, and the "real operator" part was a reference to the editor war xkcd you mentioned. Taking your post literally, I could accuse you of doing the same thing by assuming that real operators were Mac users. And I'm sure there are many use cases where hosting your own cloud storage would be absurd. But, re-reading my comment, it didn't read like a flame war instigating piece of propaganda "Calling all real sysops to arms against the user threat. Grab your cattle prod, fellow comrades, and crank up the voltage!"
> And IMHO relying on iCloud for essential data storage probably isn't a good idea.
I don't condone gatekeeping (although, to be perfectly honest, I don't see how my comment could be interpreted as elitist. It's not like the learning curve for GDrive or any other solution with a better cross platform support is _that_ high), but I don't see a reason why I shouldn't voice my opinion on a tool, even if responding to something obviously meant as a joke. Of course you're free to use iCloud. Personally, I wouldn't feel comfortable with the thought of my critical data being stored on a platform that, historically, hasn't always been known for being extremely stable. Maybe I'm just biased. If you feel comfortable using it, then use it. I won't stand in your way.
I'm admittedly sensitive to gatekeeping. I do think it's one of the biggest reasons the tech industry is the monoculture it is.
Anyhow, I appreciate you clarifying and hope I didn't offend too much.
Then come up with something better.
It will need the same or better ease-of-use for non-programmers, should function as a shell (so that their everyday use also teaches them scripting), with the same flexibility that lets people who don't care about typing or other fancy programmer stuff and just need to get work done.
If your first instinct is to say "python", you don't understand the problem.
Bash programming is not easy and full of loads of gotchas.
Useful for running multiple commands but when you start to need to do programming things like conditionals/loops/variables then using an actual programming language makes more sense.
Bash "just works" with some very well understood limitations. Every one of these "better" solutions introduces 10x the complexity with 10x the limitations (always undocumented).
After spending dozens of hours configuring them thanks to terrible documentation, generally taking multiple support tickets (if there's anyone to talk to at all), we get it working. Then, after using it for a period of time, they fall flat on their face in far worse ways than bash.
Granted I'm only managing hundreds of servers, not thousands or more, but more often than not a
for i in {000..199}; do ssh machine$i "some command"; done
is more reliable, with better error handling, than all of the other solutions.Edit: As long as linux/unix maintains the "everything is a (plaintext) file" approach, I have trouble picturing a better solution than text/string based interface like Bash. You can build more front-end complexity on top of it, but you're always going to end up back in the same place.
> for i in {000..199}; do ssh machine$i "some command"; done is more reliable, with better error handling, than all of the other solutions.
This is absolutely not the case:
From the top of my head: there is a network error that hits while running this against 5% of machines. What is the state of those servers?
If one is managing more than a few machines, one should absolutely use something like Puppet/Ansible/Salt/Chef with a repo to ensure that nothing is ever lost.
I never take it on blind faith either:
for i in {000..199}; do ssh machine$i "echo new_setting > /file"; done
for i in {000..199}; do ssh machine$i "grep new_setting /file"; done
for i in {000..199}; do ssh machine$i "reload service"; done
for i in {000..199}; do ssh machine$i "verify reload"; done
As I was taught "never make a change without verifying it". All changes are documented as well.
Maybe some. Puppet, for example, fetches a manifest and applies it on the server. Failure to apply a manifest triggers re-running the manifest. In small installations it will be asking puppetmater(s). Large installation run masterless, where they fetch the repo and run it. I would be highly surprised if Ansible did not have this mode even if it is not a popular way of doing it.
> for i in {000..199}; do ssh machine$i "echo new_setting > /file"; done > for i in {000..199}; do ssh machine$i "grep new_setting > /file"; done > for i in {000..199}; do ssh machine$i "reload service"; done > for i in {000..199}; do ssh machine$i "verify reload"; done
You are running this sequentially and by hand?! What happens when the server is dead and you need a new one? You provision it by hand and go through the stages to get it into the state where it can accept the same cattle command?
> You are running this sequentially and by hand?! What happens when the server is dead and you need a new one? You provision it by hand and go through the stages to get it into the state where it can accept the same cattle command?
Depends on the change, but generally something like that. Change + Verify. It really doesn't take much more than 10-15 minutes for any given change. We've been at a stable state for about 2 years so changes are rare at this point, other than our security update cycle.
For rebuilds, we pull periodic disk images (monthly + any major changes) and can stand up a new server in about 20 minutes, almost all of which is hands-off.
After that it takes between 60 seconds and 5 minutes for it to be inserted into service regardless of the complexity of its configuration and registration.
Places that have hundreds of bash scripts that they manually run in a loop like your example are normally a one off nightmare of random changes in random places with zero consistency, tons of edge cases, and a lot of failure scenarios.
Ansible is very entry level friendly, we've had devs grok it in 20-30 minutes and write their own playbooks. We've administrated thousands of servers at scale. We can even use it to package immutable servers with Packer and Terraform, with mostly expected results.
I'd choose the latter every time.
In the end of the day it's a collection of python modules that people have invested in that let you write steps as plainly as possible in yaml with some light logic involved.
Why reinvent the wheel?
Our experience was Ansible was amazing until it isn't. It worked for a while, then any failures would leave systems in an unknown state that had to be verified via adhoc testing like this anyway.
Then, when Ansible ran into errors, it was just a pain to get working again. Granted we haven't tried in a couple years.
Funny, because to us, Ansible just feels like reinventing the wheel. It's a more complicated tool for pushing text based changes to a group of servers.
"[W]e need to stop it with the stringly-typed, footgun-filled madness that is bash scripting" is pretty categorical.
I consider myself an accomplished system engineer and software developer. I use bash all the time. Not writing bash scripts is like saying not to use the terminal, it's just cacophony.
What have we done to deserve better? Have you seen a community of people working on a bash scripting replacement lately? Has it hit HN? Does it have the same confluence of features, convenience, portability, simplicity, and utility that bash scripting has? If so, why aren't we using it yet?
The fact is, we all use bash because it's the best we have for what we really want to do. Bash is what we deserve. 31 years of "good enough".
Ansible is written in Python. You could make the same argument.
Bash is weakly typed, which is a different concept than dynamic typing.
Dynamically typed languages prevent bugs that weakly typed languages don't, so from this point of view, the latter are unambiguously "more dangerous" than the former.
Technically speaking, even languages with strong safeguards (random examples: Golang, Rust) can't "guard against runtime errors", because it's possible to panic a program by performing an out-of-bounds array access (which classifies as "runtime error" in a very strict sense).
A concept that makes sense is the amount of safeguards provided by a language (/type system), however, this depends entirely on the use case; it's an intentional choice of the developer.
I'm not sure how not having a type system is safer than having a type system. What makes it feel more dangerous?
rm -rf $path
to go from working one day to having deleted an entire directory tree just because someone named their file "foo ."
No, it's not. It's 2020, we need to stop it with the stringly-typed, footgun-filled madness that is bash scripting. We deserve better than this.
Unless I'm missing something, this README example already contains a bug:
This will fail if the script path contains a space.