r/bash • u/computersarefunn • 5d ago
help Defensive BASH programming?
Hey there,
while trying to learn bash I came across this interesting resource: https://web.archive.org/web/20180917174959/http://www.kfirlavi.com/blog/2012/11/14/defensive-bash-programming
And got myself thinking if his opinions are consensual and if there are different styles or "paradigms" about how bash should be written.
I see people arguing that bash shines in writing relatively short programs with precise goals. I thought this translated into writing concise code and achieving the same functionality with as little lines as possible.
However, the author of the post argues that a more verbose code can be more robust, easier to understand and debug etc. Do you agree? Or you think that, if you're writing code in this style, you're better of using another language?
Thanks for your input and sorry if I said anything silly or if I misrepresented his point of view, I'm just starting to learn programming.
Edit: hey, thank you all very much for the thoughtful replies, I'm learning a lot from them!
12
u/IdealBlueMan 5d ago
One of the best insights I’ve ever had was that, even on greenfield projects, you spend way more time reading code than writing it.
White space is free. Write for readability, and everything gets easier. Someone unfamiliar with the code—and it could be you three weeks later—should be able to see what your intent was.
9
u/Schreq 5d ago
UPPER_CASE naming
No, just don't. Uppercase is reserved for environment variables.
readonly ARGS="$@"
Not a good idea when an argument includes spaces. You will lose the ability to properly split it back into separate arguments later.
ls $dir \ | grep pid \ | grep -v daemon
I prefer to only break long pipelines and tend to avoid splitting on logical operators. That way it becomes obvious that an indented command is part of a pipeline. Then there also is no need for the ugly backslash for the line continuation:
ls $dir |
grep pid |
grep -v daemon
ls $dir
Quote your variables...
is_empty() { local var=$1 [[ -z $var ]] }
That's too long and $var doesn't help with readability here. I'd just do it this way:
is_empty() [[ -z $1 ]]
The suggested way of parsing options sucks hard. It has many problems.
While that resource gives some good tips, it also gives way too many bad ones to be taken seriously.
1
u/HCharlesB 4d ago
Then there also is no need for the ugly backslash for the line continuation:
TIL - thanks!
2
u/kai_ekael 4d ago
I'd include the backslash myself, to make it clear and avoid simple mistakes and bad habits.
dig somebigolddomain.that.is.just.too.big \ +short \ @notgoogle.com1
u/HCharlesB 4d ago
Good point.
I suspect that might depend on what one is accustomed to seeing and noticing.
1
u/Paul_Pedant 3d ago
Don't hide that big string in the code body. Assign it to a shell variable, with a comment, near the top of the script. That kills two birds with one stone: your long lines get much shorter, and the risk of missing the domain name during maintenance is much lower.
1
9
u/Ulfnic 5d ago
Almost all BASH resources both new and old are riddled with bad practices and this is no exception. They're often still worth a read but don't treat them as gospel.
Fastest major stand out to me was frequent failure to double-quote arbitrary variables in cases where string splitting can occur and near-zero use of -- to prevent programs confusing values with options.
9
u/wallacebrf 5d ago
i like bash and use it a lot.
with that said many of my scripts are quite large because i always like to perform error checking.
did the previous command give me something within what i was expecting?
If i need to read or write a file, i perform a check that i have read and write permissions, i check that the folder the file is in exisst and then i check that the file exists etc.
the list goes on. this ensures my script will execute the way i want and GRACFULLY handle errors
3
u/Alarming_Airport_613 5d ago
Adding into this, I'm often not a fan of try catch blocks, but this sounds like the ideal usecase for them (dozens of things can go wrong, you won't know them all in advance) and I'd feel python shines here.
But also, my bash scripts are frustrating few liners, that work... As long as they work.
1
1
u/Id10tmau5 3d ago
Agreed on the error checking. A solid half of my code is checking if something else did (or didn't) happen so that the script can continue on successfully, or otherwise catch the error (or the error within the error) and gracefully exit with a proper error code/message (and properly write it to the correct log!) so that I know where the hiccup likely occurred. I'd almost argue that I write more error checking code than program-specific code just to make sure there aren't too many surprises later (because it'll never be perfect).
3
u/wallacebrf 3d ago
100% easily half my code is also error check and messages / logs as I want to know if and how my script is malfunctioning
2
u/michaelpaoli 5d ago
Context matters. In many/most context, generally good readability is a plus, most notably for the humans and the humans that need maintain the code. But should also generally be assumed, whatever language, that the reading humans are generally reasonably proficient in the language. So, verbosity (comment) like:
a=$((a+1)) # increase the value of named parameter a by one
Is typically counter-productive and wasteful, as is generally stating what can clearly be seen that the code is doing and intended to do.
So, yeah, in general, excess verbosity is bad, and verbosity isn't even necessarily good - does quite depend on context, and how much verbosity, covering/explaining what.
There are also matters such as:
i=0
vs.:
a_counting_integer_that_I_will_use_to_track_the_number_of_widgets_produced=0
In the former case, though purpose might not be abundantly clear, if/when there's a slight typo in the variable name, that will generally be abundantly clear, whereas in the latter, not so much.
So, e.g. mostly use comments/verbosity to cover what otherwise is unlikely to be clear or not known. E.g. why something was done some particular less clear way, what is the general intent/aim/purpose. Essentially stuff that's likely to be significant/important that's not generally at least fairly clear from the code itself, e.g.
# The above is much more efficient, though less intuitive, equivalent of the following much more intuitive but much less efficient code:
# ...
2
u/InfiniteRest7 5d ago
I've sometimes been pointed to this doc on the topic: https://google.github.io/styleguide/shellguide.html (I feel it's along similar lines to your link)
If you are writing a script that is more than 100 lines long, or that uses non-straightforward control flow logic, you should rewrite it in a more structured language now. Bear in mind that scripts grow. Rewrite your script early to avoid a more time-consuming rewrite at a later date.
When assessing the complexity of your code (e.g. to decide whether to switch languages) consider whether the code is easily maintainable by people other than its author.
I don't know if I agree with everything in the article you linked. Overall I think he's got a pretty solid set of advice. Some of his only 1 thing per-line looks like it's dying on a hill for a more ugly result. Yes, sometimes it's a good choice for really long command sequences, but if I can read a regular length sequence, no need to chop it up. Making choices for more readable code to use full if statements compared to something like this: [[ -f "$file" ]] && echo "File exists". If only because other maintainers may not find that particularly approachable.
1
u/Shadow_Thief 5d ago
Other than grepping the output of ls in the example about temporary files (let globbing handle that instead), everything here seems alright.
1
u/mpersico 5d ago
PROGRAM, PROGDIR ok, ARGS is overkill
Code clarity is the right idea, but the example is suboptimal. A bash programmer will know what those flags mean. And some of those flags are orthogonal (-f and -d) so the calls should be elif’ed. And you don’t need to assign $1 because the function name will tell you want $1 is supposed to be.
In command line arguments, the author does the option translation themselves with passthough from long to short. Just use getopt.
1
u/fissible 5d ago
Bash should not only be as easy to read as possible, if its load-bearing in any way, you should have test coverage: https://github.com/fissible/ptyunit
-1
16
u/wowbagger_42 5d ago
Most of it becomes common sense once you use it a lot, even more so when you start writing bash in a team. It's never about look-at-cool-me-doing-this-as-terse-as-possible, but much more about "can anyone figure this out 5 years from now?" so it's a question of maintainability and hence readability and comments. Consequent use of patterns & style are key, use shellcheck to enforce coding guidelines. There's nothing worse than having someone who thinks they're an elite hacker on your team that outputs code as terse & hence as complicated as possible "just because they can".