In this post I hope to convince the reader on the merits of ShellCheck. Stay tuned for more posts about using ShellCheck.

§On Shellscripting

Shell scripting is a of passion of mine. Preferably Bash (here’s a guide). (POSIX sh a.k.a. Bourne shell works too, albeit with more effort thanks to diminished versatility when compared to Bash.) The shell scripting language family has many warts as the languages were designed for both real-time interaction and automation programming. Additionally, inextricable backwards compatibility requirements are continually placed on the shell scripting language family — many Unix heads expect their shell to execute POSIX sh scripts without a hitch. Bourne shell, the sh we love and know, was developed in the early 70s and coexistent firsts in human computer interfaces.

One of my favorite shell warts is emphasis of reusing the string datatype for all sorts of operations. This wart is addressed, mildly, with Bash’s addition of arrays and more (check out declare). As a result, much of shell scripting is focused on efficient text processing and the leaky abstractions that (mostly) “typeless” programming provides. For example, in order to perform arithmetic in sh, you can use a command such as a=$(( a * 2 )) to double the value stored in variable a.

Equipped with context, it might make a little more sense as we touch on the sheer volume of pitfalls that beget robust sh or Bash utilization. Consider the first one most script writers encounter - the infamous word split. Refer to the following shell snippet:

path='/my/path with spaces'
find $path -type f -name *.[ch]

Find will chime back with an error like:

find: ‘/my/path’: No such file or directory
find: ‘with’: No such file or directory
find: ‘spaces’: No such file or directory

§Enter ShellCheck

ShellCheck is a linter for sh family languages. (A linter is a program that scans code for common mistakes.) I highly recommend it. Let’s see what the value it provides for this example:

ShellCheck catches the previous error as Double quote to prevent globbing and word splitting. [SC2086]. A brief search of SC2086 on the internet yields ShellCheck: SC2086 – Double quote to prevent globbing and word splitting. The solution here is to wrap $path in double quotes, so the script is improved to the following:

path='/my/path with spaces'
find "$path" -type f -name *.[ch]

But we’re not finished yet! Let’s say the current directory contains a file named hello.c, then the -name *.[ch] expands to -name hello.c. Woops! ShellCheck knows about this issue too: Quote the parameter to -name so the shell won't interpret it. [SC2061]. Read more here: ShellCheck: SC2061 – Quote the parameter to `-name`. The fix is the same, quote the parameter:

path='/my/path with spaces'
find "$path" -type f -name '*.[ch]'

Additional erroneous shell script examples are curated in ShellCheck’s Gallery of bad code.

§Ways to use ShellCheck

In short, apt install it! There are many other ways to run ShellCheck.

  • Check the official Installing section within the ShellCheck README.
  • Install shellcheck via your package manager (check Repology aggregated packages).
  • pip3 install shellcheck-py to install shellcheck via a Python Package (GitHub repo)
  • Run shellcheck in an unofficial Docker container (Debian based) or in the official minimal container.
  • Run in CI/CD, maybe using pre-commit. (I will be detailing this workflow in another blog post. The Installing section within the ShellCheck README details one solution which I’ll compare against other more user friendly solutions.)
  • Copy/paste into shellcheck.net’s form to check in browser. Note: the checker runs on a server so the text you pasted is exfiltrated from your browser tab.

And last but not least, be sure to run ShellCheck in your text editor or IDE. Good ShellCheck integration checks as you edit shell scripts, so you can catch errors before even saving the file.

§Suppress ShellCheck

ShellCheck offers a few different mechanisms to disable specific errors via environment variable, for the entire file, and for the next line of code. I usually reach for ignoring errors on a per-line basis:

  1. Insert a line before the offending line of the format # shellcheck disable=SC2116,SC2086
  2. ShellCheck will ignore SC2116 and SC2086 next time.
# shellcheck disable=SC2116,SC2086
hash=$(echo ${hash})    # trim spaces

(Code sample lifted from the ShellCheck wiki’s Ignore page.)

§That’s it

There is little reason to not use ShellCheck. Improve the quality of your work today. Try out ShellCheck.

Stay tuned for additional posts related to ShellCheck usage.

§P.S. If you really can’t use ShellCheck

If you can’t use ShellCheck, you can still validate that scripts parse correctly prior to execution — check out sh -n and bash -n. (See set -n in the POSIX sh docs and in the Bash infopages.) The -n flag will parse each line and inform you about parse errors, but not execute the parsed commands.