I've had a thought percolating (for many months?) but I haven't tried to phrase it and I'm leery it may sound condescending (this is also more of a public address than direct response) ...
Shellcheck tends to steer us away from "weird" parts of Bash/shell and will happily suggest changing code that was correct as written. There are good reasons for this (and I still think most scripts/projects should use Shellcheck), but Bash is a weird language, and there's a lot of power/potential in some of the weird stuff Shellcheck walls off.
If I had to nail down some heuristics for working with it...:
- do lean heavily on Shellcheck for writing new code if you're unfamiliar with the language or are only writing it under duress
- don't implement its suggestions in bulk (the time you save on iterating can easily be blown later trying to debug subtle behavior changes)
- don't apply them to code you don't understand (if you have the time and interest to understand it but find yourself stuck on some un-searchable syntax, explainshell may help)
- don't adopt them without careful testing
- do take Shellcheck warnings about code that is correct-as-written as a hint to leave a comment explaining what's going on
- do leave Shellcheck off (or only use it intermittently) if you're trying to explore, play, or otherwise learn the language
For me this is way too much to invest in Bash. Any non-trivial utilities should be written in a real programming language when possible, especially when they are used in a context where they are likely to accrete complexity over time. I’m sure there are good use cases for complex Bash programs, but we should be reaching for something with less footguns by default.
(Shellcheck is an amazing utility and I use it for all Bash that I write.)
What is? Did I suggest anyone write complex Bash programs?
Shellcheck is amazing. As I suggested in the previous post, I use it for most of my own shell scripts/projects, especially stuff I intend to release. But, because shell and Bash are weird and full of pitfalls, I think it's worth making sure people know you can't just Shellcheck-and-ship.
I think his nitpick was you promoting weird bash tricks. They're neat to use but usually a pain for someone else to maintain, as a general rule, don't use them, don't try to be clever unless you're 100% sure that it's code you'll be the only one to see (which can rarely be guaranteed).
> and there's a lot of power/potential in some of the weird stuff Shellcheck walls off.
Can you give an example? Something Shellcheck warns you away from but which, if you used it, would be better (more expressive / more powerful / more whatever) than a shellcheck-approved solution?
This gets the information without another external utility (grep, cut, sed, awk...)
It's well-past bedtime, so for now I'm just leaving the first one I can think of (it's top-of-mind since I Shellchecked it in the last couple weeks... :])
It's a little contrived in this case, but another common example is a related suggestion for `read -r`:
get_nix_version_parts(){
local major minor patch
# shellcheck disable=SC2034,SC2162
IFS="." read major minor patch < <(get_nix_version)
local -p
}
$ get_nix_version_parts
major=2
minor=3
patch=4
Using -r there works fine for me, though your SC2034 complaint is valid, in that it essentially prevents from you from using a very specific built in formatter provided by "local -p".
This feels like a rare case, and I'd have no problem doing the "disable=SC2034" there, but I'd add a note explaining why.
Even then, one could make an argument (I think I would, actually) that an explicit printf is clearer and more robust. For example, it gives you more control over the formatting, and it doesn't introduce implicit coupling between the output of "local -p" and our function, which in theory could change in the future or be different on different bash versions, etc. I suspect this probability is very low in the particular case of local -p, but still it's not a great practice. Not something I want to give the stamp of approval of inside my code base.
get_nix_version_parts(){
local major minor patch
IFS="." read -r major minor patch < <(nixv)
printf '%s=%s\n' major "$major" minor "$minor" patch "$patch"
}
Good catch on -r not mattering, here. I think `read` is one I've had trouble with and assumed that was it since I bothered disabling it. But after grepping around a bit more, the only one I see that shellcheck would object to that actually differs isn't common enough that I'd gripe about it.
I don't actually have a gripe with 2034, here. It's a good example of a statement that the user obviously has to triage.
Likewise, I'm just using `local -p` as a cheap way to show that the variables populate, it wasn't part of the original code I adapted. Yes--the case for coupling to the output of local -p is when the output is going to be used to set variables again later.
Going back to intentionally look through a few things for examples is helping me better clarify why I've had this impression slowly building up...
1. Some Shellcheck code titles/short messages communicate uncertainty well, but some others can read as more confident/absolute than the situation merits.
2. Broadly, Shellcheck tends to steer people away from word-splitting. Word-splitting can be a surprising PITA, so I get it. But, it's also a critical concept for understanding shell.
If you are reluctantly writing Bash/shell, it's good to have Shellcheck help you avoid it. If you want or need to understand the language, you're going to have to grapple with word-splitting to get there.
Fair :) but I think there's a degree difference here (and this is more about the language than Shellcheck itself).
Also, maybe my original phrasing left room for misinterpretation? Shellcheck will happily suggest changes that break working code. I think I've had it happen ~4 times this year?
Obviously it varies a little by tool type and ecosystem--it's obviously not much of a surprise if a dead-code analysis tool suggests removing things you can't actually cut, and likewise linters in most languages can point out unused variables/parameters that you can't cut. But I can't recall the last time another tool gave me specific do-x-not-y suggestions that just weren't anywhere close to fungible.
> Shellcheck will happily suggest changes that break working code.
Which is OK once it is known. Your example nicely shows that. The flagged behavior is often unexpected, so there where it is expected having shellcheck line that turns off the warning greatly improves readability of the whole script, essentially showing the reader "here is the dependence on non obvious behavior."
Where such dependence exists, I prefer to see the "shellcheck" line before pointing to it (by turning that specific warning off exactly in that line). It's then a "vetted" and "explained" line.
Shellcheck tends to steer us away from "weird" parts of Bash/shell and will happily suggest changing code that was correct as written. There are good reasons for this (and I still think most scripts/projects should use Shellcheck), but Bash is a weird language, and there's a lot of power/potential in some of the weird stuff Shellcheck walls off.
If I had to nail down some heuristics for working with it...:
- do lean heavily on Shellcheck for writing new code if you're unfamiliar with the language or are only writing it under duress
- don't implement its suggestions in bulk (the time you save on iterating can easily be blown later trying to debug subtle behavior changes)
- don't apply them to code you don't understand (if you have the time and interest to understand it but find yourself stuck on some un-searchable syntax, explainshell may help)
- don't adopt them without careful testing
- do take Shellcheck warnings about code that is correct-as-written as a hint to leave a comment explaining what's going on
- do leave Shellcheck off (or only use it intermittently) if you're trying to explore, play, or otherwise learn the language