Defensive BASH Programming: The Reprint

The aim of this page📝 is to rewrite Web Archive > Defensive BASH programming > Say what? as it is no more publicly available since the domain is down at the moment.

There’s also Defensive BASH Programming which is older. There’s also a critique on hackernews Defensive BASH programming | Hacker News. Let’s just transcribe. I am also changing the formatting as per shellcheck’s recommendations.

  • Try to keep globals to minimum
  • Globals use UPPER_CASE naming convention
  • Use readonly declaration

Same as declare -r, sets a variable as read-only, or, in effect, as a constant. Attempts to change the variable fail with an error message. This is the shell analog of the C language const type qualifier.

https://tldp.org/LDP/abs/html/internal.html

  • Use globals to replace the cryptic $0, $1, etc. names passed as arguments into the script call
  • Globals the author always uses in his programs are as follows

Positional parameters: $0 is the name of the script itself, $1 is the first argument, $2 the second, $3 the third, and so forth. After $9, the arguments must be enclosed in brackets, for example, ${10}, ${11}, ${12}. The special variables $* and $@ denote all the positional parameters.

  • the local keyword cannot be used within global scope
pkutaj@DESKTOP-LRK1G7U:~$ local foo
-bash: local: can only be used in a function
  • Self-documenting parameters
  • Usually for loop use the conventional i variable ⟹ makes it essential to declare it as local

On Unix-like operating systems, shift is a builtin command of the Bash shell. When executed, it shifts the positional parameters (such as arguments passed to a bash script) to the left, putting each parameter in a lower position.

  • Intuitive for functional programmers
  • Helps keeping all variables local
  • As outlined, only 2 things are running globally ..1. Global immutables ..2. main which is calling main()
  • We may start with
  • but improve this with
  • in the second example, finding fines is the problem of get_temp_files() and not of main()
  • the code also becomes testable, by unit testing of get_temp_files()
  • Debugging is activated via the -x flag
bash -x my_prog.sh
  • Don’t do the above
  • Instead, debug just a small section of code wrapping it into set -x and set +x
  • In addition, you can use the $FUNCNAME + $@ variables and print them while debugging
  • You’ll get something like
temp_files /var/tmp
+ ls /var/tmp
+ grep pid
+ grep -v daemon
+ set +x
  • can you always quickly say does the following code do?
  • of course, disagreements rage around this one on HN
  • Let your code speak
  • Break expressions with \ or with natural line continuators
  • For example

Can be written much cleaner — shellcheck removes backslash and puts the pipe at the end of the line and I am keeping this as opposed to the original doc

  • Good example where we see the connection between lines and the connecting rods.

Here is the conflict with shellcheck. See the diff — I suggest that the point is made and both are fine

  • Do not do the following
  • It should be a function
  • Here is an example to complement the usage function above.
  • You use it like this, using the immutable ARGS variable we defined at the top:
main() {
cmdline $ARGS
}
main
  • Here is another example using df command:
  • Here I have an exception, for testing, I declare DF in the global scope not readonly.
  • This is because of shunit2 not allowing to change global scope functions.

--

--

Infrastructure Support Engineer/Technical Writer (snowplow.io) with a passion for Python/writing documentation. More about me: https://pavol.kutaj.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Pavol Kutaj

Infrastructure Support Engineer/Technical Writer (snowplow.io) with a passion for Python/writing documentation. More about me: https://pavol.kutaj.com