ShellCheck finds bugs in your shell scripts by running them through a static analysis tool.

#!/bin/bash
# This script has a common mistake

echo "Hello, world!"
variable="some value"
if [ $variable = "some value" ]; then
  echo "It matches!"
fi

# Let's try to lint this
shellcheck your_script_name.sh

When you run shellcheck your_script_name.sh, you might see output like this:

In your_script_name.sh line 5:
if [ $variable = "some value" ]; then
     ^-----------------------^ SC2086: Double quote to prevent globbing and word splitting.

In your_script_name.sh line 5:
if [ $variable = "some value" ]; then
     ^-----------------------^ SC2143: Use < or > for string comparison.

This output tells you exactly where the problem is and what the issue is. The first message, SC2086, is about word splitting and globbing. The second, SC2143, is about string comparison.

Let’s fix these. The most common error is not quoting variables. In shell scripting, variables are expanded before the command is run. If the variable contains spaces or special characters, it can lead to unexpected behavior.

The Fix for SC2086:

#!/bin/bash

echo "Hello, world!"
variable="some value"
# Always quote variables when they are used as arguments to commands
if [ "$variable" = "some value" ]; then
  echo "It matches!"
fi

By adding double quotes around $variable, we ensure that the shell treats the entire value of the variable as a single string, even if it contains spaces. This prevents word splitting and globbing, where the shell might interpret parts of the variable’s value as separate arguments or file patterns.

The Fix for SC2143:

#!/bin/bash

echo "Hello, world!"
variable="some value"
# Use = for string equality comparison inside `[` or `[[`
if [ "$variable" = "some value" ]; then
  echo "It matches!"
fi

The [ command (also known as test) uses = for string equality. The < and > operators are for numerical comparison. ShellCheck flags this because using < or > for strings can lead to errors or unexpected results if the strings happen to be interpreted as numbers.

Putting it Together:

#!/bin/bash

echo "Hello, world!"
variable="some value"
if [ "$variable" = "some value" ]; then
  echo "It matches!"
fi

Running shellcheck your_script_name.sh on this corrected script will produce no output, meaning ShellCheck found no issues.

Here are some other common issues ShellCheck catches and how to fix them:

SC2034: Variable appears unused.

This happens when you define a variable but never use it.

  • Diagnosis: shellcheck your_script.sh will output SC2034: variable_name appears unused.
  • Fix: Either use the variable or remove its definition. If it’s a placeholder for future use, you can prefix it with an underscore (_variable_name) to tell ShellCheck it’s intentional.
    # Original:
    unused_var="oops"
    echo "Hello"
    
    # Fixed:
    # unused_var="oops" # Removed
    echo "Hello"
    
  • Why it works: Removing unused variables cleans up your code and prevents potential confusion. Prefixing with _ tells the linter to ignore it.

SC2154: variable is used but not set.

This is the opposite of SC2034 – you’re trying to use a variable that hasn’t been assigned a value.

  • Diagnosis: shellcheck your_script.sh will output SC2154: variable_name is used but not set.
  • Fix: Ensure the variable is set before it’s used. This might involve assigning it a default value or ensuring it’s set by a previous command.
    # Original:
    echo "The value is: $my_var" # my_var is not set
    
    # Fixed:
    my_var="default"
    echo "The value is: $my_var"
    
  • Why it works: Assigning a value ensures the variable has content when echo tries to print it, preventing unexpected output or errors.

SC2086: Double quote to prevent globbing and word splitting.

We saw this earlier, but it’s worth reinforcing. It applies to any variable expansion used as an argument.

  • Diagnosis: shellcheck your_script.sh will output SC2086: Double quote to prevent globbing and word splitting.
  • Fix: Always double-quote variable expansions when they are passed as arguments to commands.
    # Original:
    filename="my file with spaces.txt"
    ls $filename # Potential problem if filename has spaces
    
    # Fixed:
    filename="my file with spaces.txt"
    ls "$filename"
    
  • Why it works: ls $filename might be interpreted by the shell as ls my file with spaces.txt, which ls would see as three separate arguments. ls "$filename" correctly passes the single string "my file with spaces.txt" to ls.

SC2164: Use 'cd … || exit' or 'cd … || return' to avoid continuing with wrong directory.

When you change directories, it’s crucial to ensure the cd command succeeded. If it fails (e.g., the directory doesn’t exist), subsequent commands might run in the wrong place, causing havoc.

  • Diagnosis: shellcheck your_script.sh will output SC2164: Use cd ... || exit' or 'cd ... || return' to avoid continuing with wrong directory.
  • Fix: Chain cd with || exit or || return (in functions).
    # Original:
    cd /path/to/my/directory
    # ... many commands that assume they are in the directory ...
    
    # Fixed:
    cd /path/to/my/directory || exit 1
    # ... commands ...
    
  • Why it works: The || exit 1 part means "if the cd command fails (returns a non-zero exit code), then execute exit 1." This immediately stops the script if the directory change doesn’t work, preventing it from proceeding in an incorrect location.

SC2016: Errors in expression: Use single quotes to prevent string expansions.

This one is a bit counter-intuitive. It often appears when you’re trying to use single quotes within a command substitution or variable expansion.

  • Diagnosis: shellcheck your_script.sh might output SC2016: Errors in expression: Use single quotes to prevent string expansions. often with $(echo 'some string').
  • Fix: If you need literal single quotes inside a string that’s already enclosed in double quotes, you can escape them or use a different quoting strategy. More commonly, it means you intended to have a literal string but accidentally used double quotes where single quotes were needed.
    # Original (problematic for some commands):
    echo "User's name is $(echo 'John Doe')"
    
    # Fixed:
    echo "User's name is $(echo 'John Doe')" # This is often fine, but SC might flag it if the context implies a literal string was intended.
    # A better fix if you just want the string directly:
    my_name='John Doe'
    echo "User's name is $my_name"
    
    The more common SC2016 scenario is when you have a variable that needs to be passed to a command that interprets its arguments, and you want to preserve literal strings.
    # Original:
    command="grep 'pattern' file.txt"
    $command # ShellCheck might warn about SC2016 here if the command is complex
    
    # Fixed:
    pattern='specific pattern'
    grep "$pattern" file.txt
    
  • Why it works: ShellCheck is trying to prevent subtle bugs where shell expansions happen unexpectedly. By enforcing consistent quoting, it ensures that strings are treated as intended, especially when they contain characters that the shell might otherwise interpret.

ShellCheck is incredibly powerful because it doesn’t just look for syntax errors; it understands shell semantics and common pitfalls. Integrate it into your workflow, and your shell scripts will become much more robust.

The next error you’ll likely encounter after fixing these common issues is related to set -e and how it interacts with commands that can legitimately return non-zero exit codes.

Want structured learning?

Take the full Bash course →