Why this lesson
By now you know a dozen ways a script can go wrong quietly: missing quotes, local x=$(cmd), an unguarded cd. Nobody remembers all of them while typing. So you let tools check for you, and you learn to watch a script run step by step when it surprises you.
What you need to know already: everything in this chapter so far; installing a package with apt (1.11).
Three tools, three questions
shellcheck script.sh "is this correct?" - static analysis, finds real bugs
bash -n script.sh "does it parse?" - syntax only, runs nothing
bash -x script.sh "what did it DO?" - trace every command as it runs
- Static analysis means reading the code without running it, looking for known mistakes. shellcheck is the static analyser for shell scripts.
- Syntax is the grammar: a missing
fiordoneis a syntax error.bash -n(no-exec) only checks the grammar. - Tracing (
-x, "xtrace") prints each command, after expansion, just before bash runs it - so you see what really happened.
shellcheck codes you will see constantly
Every finding has a code, SC + four digits. You can look any of them up by code. The ones this chapter has already shown you:
SC2148 missing shebang
SC2086 unquoted variable - word splitting and globbing
SC2046 unquoted $(...) - same problem
SC2006 backticks; use $(...)
SC2155 local x=$(cmd) masks the exit code
SC2164 cd without || exit
SC2162 read without -r
SC2115 "rm -rf $dir/" - use "${dir:?}" so an empty var cannot become /
SC2002 useless cat
SC2181 checking $? instead of testing the command directly
(Backticks - a command between two backtick characters - are the old way to write $(cmd). A "useless cat" is cat file | grep x where grep x file would do.)
Every one is a real bug someone has shipped. Install it (sudo apt install shellcheck) and run it on every script before you trust it - it takes seconds and catches the class of mistake that only shows up with an unusual filename or an empty variable.
A false positive is a finding that is wrong for your case. Silence one deliberately and narrowly with a comment:
# shellcheck disable=SC2086
directly above the line, never at the top of the file.
bash -x is the debugger
$ bash -x deploy.sh -e prod
+ env=
+ tag=latest
+ getopts :e:t:nh opt
+ env=prod
+ echo 'deploying latest to prod'
Each + line is one command, after expansion, so you see what it actually became - which is usually where the surprise is (+ env= shows env started empty). The trace goes to stderr. Turn it on for part of a script with set -x ... set +x.
PS4 is the text printed at the start of each trace line (default + ). Adding the file name and line number makes long traces readable:
export PS4='+ ${BASH_SOURCE}:${LINENO}: '
(BASH_SOURCE is the script's file name, LINENO the current line number; the single quotes delay their expansion until each trace line is printed.)
bash -n
Reads the whole script without running anything. It is instant, and the only one of the three that is safe to run against a script you have not read.
It only catches syntax, not sense: rm -rf / passes bash -n happily.
What you can now do
- Run shellcheck and turn each SC code into a fix.
- Use
bash -nfor a syntax check andbash -xto see what a script really ran.