ShellCheck

From LinuxReviews
Jump to navigationJump to search
ShellCheck
Original author(s)Norwegian road.jpeg
The Norwegians, specifically
Vidar Holen
Repositorygithub.com/koalaman/shellcheck
Written inHaskell
LicenseGPLv3
Documentationgithub.com/koalaman/shellcheck/blob/master/README.md
Websitewww.shellcheck.net
Konsole.svg

ShellCheck is like a SpellCheck for shell scripts. It will analyze your shell scripts and point out many of the embarrassing mistakes, flaws and potential corner-case problems you made writing them, and propose changes. ShellCheck is not a magic bullet that will detect and point out everything that is wrong with any and all shell scripts, and a shell script may not work as expected, or at all, even if ShellCheck fails to produce any warnings. It does catch a lot, and it is absolutely worth using on every shell script you write or use even if your shell scripts seemingly look and work just fine.

ShellCheck is not for everyone, and understanding it's output does require some computer knowledge. It isn't for you if you have no idea what a shell or a shell script is. You may want to read Bash Guide for Beginners if that's the case. Everyone who has written, or just used, a shell script will find ShellCheck to be a great, and perhaps essential, tool.

Features And Usability

Shellcheck-checking-02.jpg
ShellCheck checking a shell script for errors.

Writing a simple shell script that will run on just about every Linux machine you will encounter seems really easy the first time you encounter the concept. Just write #!/bin/sh into a file and a series of commands you'd like to be executed in sequence, save it and volla, you're done.

File: MyFirstShellScript.sh
#!/bin/sh
echo Hello world!
echo look at all these files!!!
ls

Just chmod +x MyFirstShellScript.sh to make it executable and run it with

./MyFirstShellScript.sh

..and now you're a "bash programmer". That proudness won't last long, you will quickly realize that shell scripting is, in fact, not that easy:

File: MySecondShellScript.sh
#!/bin/sh
echo Hello world! I can manipulate files!
echo This is my file!! > my file.txt

This code may look super duper at first glance, but there are some minor problems with it. First of all, no file named my file.txt will be created. What you get is a file called my and it will contain:

This is my file!! file.txt

You may wonder what goes wrong with that script and check it with ShellCheck. That won't help, shellcheck MySecondShellScript.sh will not produce any warnings in this case. It's fine as far as ShellCheck is concerned.

ShellCheck will not a magic program that will magically detect everything that is wrong and broken in any and all shell scripts. It is not some kind of artificial intelligence that will deduce what you are trying to do and explain what you are doing wrong and how to do it right in any and all cases.

Now, let's make a script that is somewhat similar to the one above:

File: ShellScriptWithVariables.sh
#!/bin/sh
echo "Hello world! I can manipulate files!"
text = "This is my file!"
file = "my file.txt"
echo $text > $file

...and run shellcheck ScriptWithVariables.sh on that one - now the potential usefulness of ShellCheck becomes more apparent:

In ScriptWithVariables.sh line 2:
text = "This is my file!"
     ^-- SC1068: Don't put spaces around the = in assignments (or quote to make it literal).


In ScriptWithVariables.sh line 3:
file = "my file.txt"
     ^-- SC1068: Don't put spaces around the = in assignments (or quote to make it literal).


In ScriptWithVariables.sh line 4:
echo $text > $file
     ^---^ SC2086: Double quote to prevent globbing and word splitting.
             ^---^ SC2086: Double quote to prevent globbing and word splitting.

Did you mean: 
echo "$text" > "$file"

For more information:
  https://www.shellcheck.net/wiki/SC1068 -- Don't put spaces around the = in ...
  https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...

Now we can use the proposals from ShellCheck and fix our little script so there are no spaces around the = and quotes around the variables (see Bash Guide for Beginners Chapter 3, "Quoting characters"):

File: ShellScriptWithVariables.sh
#!/bin/sh
echo "Hello world! I can manipulate files!"
text="This is my file!"
file="my file.txt"
echo "$text" > "$file"

ShellCheck does pick up on a lot of things you may not notice or think about when you write a bash script. It won't magically understand the intention of every line and fix every little error for you, but there is a lot of things it will catch and point out.

Shebang Awareness

ShellCheck cares about the shell you specify with #! in the very first line of your shell scripts.

Most GNU/Linux distributions have a symbolic link pointing from /bin/sh to /bin/bash. You can typically start your scripts with #!/bin/sh and use all kinds of advanced bash extensions since it is bash, not some ancient POSIX compliant Bourne shell binary, that will interpret your shell script.

ShellCheck assumes that you will be using the ancient Bourne shell, or another POSIX compliant shell, if you use the #!/bin/sh shebang. It will point out potential problems, that won't apply to your machine, if you use #!/bin/sh with the assumption that it will always be bash who is interpreting your scripts. Consider this:

#!/bin/sh
function runbenchmark(){
  for f in 20-anime-girls-with-questionmarks/*.jpg;do
    outputf="output${1}/${f/20-anime-girls-with-questionmarks\//}"
    echo -n .
    "$HOME/bin/realsr-ncnn-vulkan" \
      -m "$HOME/bin/realsr-ncnn-vulkan-models/models-DF2K_JPEG" \
      -g "${1}" -i "$f" -o "$outputf" 2>/dev/null
  done
  echo
}

ShellCheck will, in the case of the above script with a single function and nothing using it, point out that:

In 1.sh line 2:
function runbenchmark(){
^-- SC2112: 'function' keyword is non-standard. Delete it.


In 1.sh line 4:
    outputf="output${1}/${f/20-anime-girls-with-questionmarks\//}"
                        ^-- SC2039: In POSIX sh, string replacement is undefined.


In 1.sh line 5:
    echo -n .
         ^-- SC2039: In POSIX sh, echo flags are undefined.

ShellCheck is correct, there would be a problem if that function where ever used in a machine where #!/bin/sh isn't a symbolic link to bash. Replacing #!/bin/sh with #!/bin/bash makes those errors go away.

ShellCheck will identify sh (seen as POSIX), bash, ksh and dash shells. Any support for other shells is not indicated in src/ShellCheck/Checker.hs.

ShellCheck will also warn you if you do not have any shebang. A single one-liner like:

QT_AUTO_SCREEN_SCALE_FACTOR=1 "$HOME/bin/monero-wallet-gui" &

makes ShellCheck tell you that:

In monero line 1:
QT_AUTO_SCREEN_SCALE_FACTOR=1 "$HOME/bin/monero-wallet-gui" &
^-- SC2148: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.

For more information:
  https://www.shellcheck.net/wiki/SC2148 -- Tips depend on target shell and y...

Specifying what shell should be used may not seem like a big deal, and it's probably not if you'll stick to the same machine running the same operating system forever. Problems can and probably will arise the moment you copy a script that doesn't specify what shell should run it from one box to a very different box. That is specially true if the default shells on those boxes differ.

Checking Multiple Files

ShellCheck accepts long lists of files as arguments. You can use wildcards and check entire folders at once.

Limitations

ShellCheck won't find every little thing that's wrong with your scripts. It's not magic. And it will even get things wrong. Consider this:

#!/bin/bash                                                                                                             
parallel -j$(nproc)  convert {} {.}.webp ::: *.png

ShellCheck believes the GNU Parallel statement is flawed and warns that:

In png2webp.sh line 2:                                                                                                  
parallel -j$(nproc)  convert {} {.}.webp ::: *.png                                                                      
           ^------^ SC2046: Quote this to prevent word splitting.                                                       
                                ^-- SC1083: This { is literal. Check expression (missing ;/\n?) or quote it.
                                  ^-- SC1083: This } is literal. Check expression (missing ;/\n?) or quote it.

There is no reason to check that expression. GNU Parallel works like that. It's fine.

You can't expect that ShellCheck will know what every random program your scripts call with arguments do with those arguments. It may get some things wrong. It is somewhat right about -j$(nproc) even though $(nproc) will always be a whole number of available CPU threads. -j$(nproc) is the default -j option for GNU Parallel, so it doesn't need to be there at all.

Verdict And Conclusion

ShellCheck is something you should install and use if you write shell scripts - even if they are mostly very small and seemingly trivial. Even something as short and silly as:

#!/bin/bash
for f in *.txt ;do
  cat $f
done

can work fine and seem fine - and it's all good until someone drops a file named "my file.txt" into the folder where it runs. ShellCheck will tell you things like

In catthemall.sh line 3:
  cat $f
      ^-- SC2086: Double quote to prevent globbing and word splitting.

and a lot of other useful things you may not notice. Consider this:

#!/bin/bash

if [ ! -f{${1} ];then
  exit 0
fi

ffmpeg -i "$1" -c:v libvpx-vp9 \
  -b:v 0 -crf 18 \
  -threads 4 \
  -speed 1 \
  -f webm "/tmp/gif2webm.webm"

ffmpeg -i /tmp/gif2webm.webm -map 0:0   -c:v libvpx -b:v 2M "${1}.webm"
rm /tmp/gif2webm.webm

So you see anything wrong with the above? There is something wrong, and ShellCheck would tell you:

In gif2webm.sh line 3:
if [ ! -f{${1} ];then
         ^-- SC1035: You are missing a required space here.
         ^-- SC1083: This { is literal. Check expression (missing ;/\n?) or quote it.
          ^--^ SC2086: Double quote to prevent globbing and word splitting.

The if statement if [ ! -f{${1} ];then will actually always be false since -f{${1} will always be true and we are checking for not true with [ ! ... ].

if [ ! -f "${1}" ];then makes the -f expression do what you would expect it to do: check if the scripts first argument is a file in the current folder.

The erroneous if statement is a pretty good example of why using ShellCheck is a good idea: bash won't tell you that there's anything wrong with -f{${1}, it will just interpret it as true and move along. ShellCheck can help you figure out what's going wrong in cases where there are no apparent errors from bash or other shells yet the script isn't doing what you expect it to do.

We can strong recommend ShellCheck for both Linux novices who just begun reading Bash Guide for Beginners and experienced shell "programmers" who know the Advanced Bash-Scripting Guide by heart.

Installation

All the major GNU/Linux distributions have it in their repositories, though the name will vary. Some call it shellcheck (Debian, Arch, Gentoo, OpenBSD), some call it ShellCheck (RHEL, Fedora, openSUSE) and FreeBSD calls it hs-ShellCheck. iToddlers can install it as shellcheck using something called "brew".

Links


Add your comment
LinuxReviews welcomes all comments. If you do not want to be anonymous, register or log in. It is free.