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.

Pavol Kutaj
3 min readFeb 24, 2023

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.

1. Make global variables immutable

  • 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.

2. All variables should be local

  • 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.

3. Use the top-level main() function — the only global command in the code is main

  • Intuitive for functional programmers
  • Helps keeping all variables local

4. Everything except global immutables and calling main is in a function

  • 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()

5. Instead of using -x for debugging the whole file, wrap problematic sections into set -x and set +x for precision.

  • 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

6. Extract commands into names with readable functions so that code reads “like newspaper”

  • can you always quickly say does the following code do?
  • of course, disagreements rage around this one on HN
  • Let your code speak

7. Each line does just one thing

  • 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

8. Print usage with heredoc within a dedicated function

  • Do not do the following
  • It should be a function

9. Command line arguments

  • 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

10. Unit Testing

  • 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.

LINKS

--

--

Pavol Kutaj

Today I Learnt | Infrastructure Support Engineer at snowplow.io with a passion for cloud infrastructure/terraform/python/docs. More at https://pavol.kutaj.com