I hope you use ShellCheck
Updated Sunday, Sep 8, 2024
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:
- Insert a line before the offending line of the format
# shellcheck disable=SC2116,SC2086
- ShellCheck will ignore
SC2116
andSC2086
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.