Post

Shellcheck - A Static Analysis Tool for Shell Scripts - Introduction

Shellcheck - A Static Analysis Tool for Shell Scripts - Introduction

ShellCheck is a static analysis tool, or linter, for shell scripts. It detects various types of errors, provides suggestions, and issues warnings for shell scripts. ShellCheck identifies syntax issues, semantic problems that may cause shell scripts to behave unexpectedly, and other corner cases.

Different examples of poorly written shell code are presented in the following sections. The utilization of ShellCheck to identify errors, offer suggestions, and issue warnings in these examples is detailed.

The currently supported shells include: bash, dash and ksh.

Installation

To install ShellCheck on Ubuntu/Debian-based Linux systems, use the following command:

1
sudo apt install shellcheck

Usage of Shellcheck with bad code examples

Create a demo directory to practice the ShellCheck tool with the following commands:

1
2
mkdir -p ~/shellcheck_demo
cd ~/shellcheck_demo

Checking quotes

Here is a shell script named quotes.sh that contains various poorly written examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash

# Example 1
print_seed() {
    cat $1
}

echo "animal mammal dinner hot now yesterday" > "Seed Phrase"

print_seed "Seed Phrase"

# Example 2
touch file1.txt file2.txt
find ./ -name *.txt

# Example 3
touch "~/myfile.txt"

# Example 4
echo 'The PATH is $PATH'

Attempt to execute this script and observe if the output aligns with your expectations.

1
2
chmod +x ./quotes.sh
./quotes.sh

Checking the script with shellcheck

Now, let’s identify the issues in the above script using the shellcheck command.

1
shellcheck quotes.sh

Here is the report:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
In quotes.sh line 5:
    cat $1
        ^-- SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean:
    cat "$1"


In quotes.sh line 14:
find ./ -name *.txt
              ^---^ SC2061 (warning): Quote the parameter to -name so the shell won't interpret it.
              ^-- SC2035 (info): Use ./*glob* or -- *glob* so names with dashes won't become options.


In quotes.sh line 17:
touch "~/myfile.txt"
       ^----------^ SC2088 (warning): Tilde does not expand in quotes. Use $HOME.


In quotes.sh line 20:
echo 'The PATH is $PATH'
     ^-----------------^ SC2016 (info): Expressions don't expand in single quotes, use double quotes for that.

For more information:
  https://www.shellcheck.net/wiki/SC2061 -- Quote the parameter to -name so t...
  https://www.shellcheck.net/wiki/SC2088 -- Tilde does not expand in quotes. ...
  https://www.shellcheck.net/wiki/SC2016 -- Expressions don't expand in singl...

Analyzing above report

The output provides a clear explanation of the identified issues. Nevertheless, let’s delve into the details for a better understanding:


In the example 1 - line 5: $1 should be in quotes.
In the example 2 - line 14: Wrap *.txt in the quotes.
In the example 3 - line 17: Tilde won’t exapand inside the double quotes. We can use $HOME
In the example 4 - line 20: $PATH won’t expand in single quotes. Use double quoes.

Here is the updated quotes.sh script after addressing the errors/warnings pointed out by the shell script tool:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash

# Example 1
print_seed() {
    cat "$1"
}

echo "animal mammal dinner hot now yesterday" > "Seed Phrase"

print_seed "Seed Phrase"

# Example 2
touch file1.txt file2.txt
find ./ -name "*.txt"

# Example 3
touch "$HOME/myfile.txt"

# Example 4
echo "The PATH is $PATH"

Checking conditionals

Here is the conditionals.sh script with examples illustrating various conditional-related issues.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash

# Example 1
n=2
if [[ n != 2 ]]; then
    echo "n is not 2";
else
    echo "n is 2";
fi

## `[[ n != 2 ]]` returns true always. And echo statement will not print.

# Example 2
touch file1.txt

if [[ -e *.txt ]]; then
    echo "file1.txt present"
else
    echo "file1.txt not present"
fi

# Example 3
n=3
if [[ ${n}==2 ]]; then
    echo "n is 2";
else
    echo "n is not 2";
fi

# Example 4

n=""
if [[ -n "$n " ]];then
    echo "n is not empty"
else
    echo "n is empty"
fi

Running shellcheck on the script

Run the script and verify if the output aligns with expectations

1
shellcheck conditionals.sh

Here is the analysis:

In example 1 - line 5: It should be $n instead of n.
In example 2 - line 16: -e can’t be used with *.txt. Either use file path or use for loop to check all the *.txt files.
In example 3 - line 24: There should be spaces around the comparison operator.
In example 4 - line 33: The if condition is always true regardless of n value because there is a space inside the quotes. Remove it.

Shellcheck can identify various types of conditional issues, including the following.

  • Comparing numerical values with strings
  • Using logical operators inside brackets.
  • Accidental backgrounds and piping [[ condition1 ]] & [[ condition 2 ]] | [[ condition 3 ]]

Checking syntax errors

Here are a few examples of syntax issues in shell scripts. Refer to the comments for the corresponding fixes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
n = 42                          # Spaces shouldn't be there around = operator while assignments
$n=42                           # $ should not be used when assigning a value to a variable

# Shouldn't use $ with loop variable (i). Following is wrong
for $i in {1..10};do
    # Do something here
done

n=1
num$n="36"                      # Use arrays instead

arr=(1, 2, 3)                   # Commas can't be used when defining arrays
echo $arr[14]                   # Missing {} in array references

else if condition; then ..      # Using 'else if' instead of 'elif'

func; func() { echo "hello world; }     # Using function before definition

Checking protability between shells

Here are a few examples that may run without issues in one shell variant but pose problems in others. Shellcheck does a good job of detecting these issues.

1
2
echo {1..10}                    # Works in bash and ksh. Doesn't work in sh or dash
local var=2                     # local keyword is undefined in sh

Miscellaneous checking

Following are few more bad examples:

1
2
3
4
5
6
7
echo 'Don't do it'              # Three apostrophes. Unclosed single quote.
cat file1.txt | grep word       # cat is unnecessary. Use - grep word file1.txt
echo "Today date is `date`"     # Use $ instead of backticks for date command
echo $(( $a + 2 ))              # Don't use $ on variables inside the $((..)).
                                # Might not work in all shells.
rm -rf "$PROJECTROOT/"*         # Warns user the possibility of removing root directory if PROJECTROOT is empty

Getting additional help on shellcheck

The shellcheck help command

The shellcheck --help command gives the brief info and options supported for the shellcheck tool.

1
shellcheck --help
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Usage: shellcheck [OPTIONS...] FILES...
  -a                  --check-sourced            Include warnings from sourced files
  -C[WHEN]            --color[=WHEN]             Use color (auto, always, never)
  -i CODE1,CODE2..    --include=CODE1,CODE2..    Consider only given types of warnings
  -e CODE1,CODE2..    --exclude=CODE1,CODE2..    Exclude types of warnings
  -f FORMAT           --format=FORMAT            Output format (checkstyle, diff, gcc, json, json1, quiet, tty)
                      --list-optional            List checks disabled by default
                      --norc                     Don't look for .shellcheckrc files
  -o check1,check2..  --enable=check1,check2..   List of optional checks to enable (or 'all')
  -P SOURCEPATHS      --source-path=SOURCEPATHS  Specify path when looking for sourced files ("SCRIPTDIR" for script's dir)
  -s SHELLNAME        --shell=SHELLNAME          Specify dialect (sh, bash, dash, ksh)
  -S SEVERITY         --severity=SEVERITY        Minimum severity of errors to consider (error, warning, info, style)
  -V                  --version                  Print version information
  -W NUM              --wiki-link-count=NUM      The number of wiki links to show, when applicable
  -x                  --external-sources         Allow 'source' outside of FILES
                      --help                     Show this usage summary and exit

We can make shellcheck ignore certain error codes using the option –exclude=CODE1,CODE2… Useful while deploying the shellcheck in some build system.

Report from the shellcheck output

If you review the Shellcheck output for the quotes.sh script, each error, warning, or suggestion is accompanied by a URL at the end of the report for additional reference. It looks something like the following:

1
2
3
4
For more information:
  https://www.shellcheck.net/wiki/SC2061 -- Quote the parameter to -name so t...
  https://www.shellcheck.net/wiki/SC2088 -- Tilde does not expand in quotes. ...
  https://www.shellcheck.net/wiki/SC2016 -- Expressions don't expand in singl...

Feel free to open those URLs in the browser and read the detailed info about the issue there.

Page consisting of all error codes

You can find the all shellcheck error codes here. If this link is broken, you can find the modified web page link here

This post is licensed under CC BY 4.0 by the author.