← Back to DevBytes

ShellCheck: Complete Configuration Guide

ShellCheck: Complete Configuration Guide

ShellCheck is a static analysis tool that finds bugs and suspicious patterns in shell scripts. Whether you write a one-liner in Bash or maintain thousands of lines of POSIX sh, ShellCheck catches common mistakes before they reach production. This guide walks through everything from installation to advanced configuration, with practical examples you can apply immediately.

What Is ShellCheck?

ShellCheck is an open-source linting tool written in Haskell. It parses shell scripts and reports issues ranging from quoting problems and unsafe variable expansion to deprecated syntax and portability concerns. It supports Bash, Dash, ksh, and other POSIX-compatible shells, and it integrates with virtually every major editor, CI system, and build tool.

Each finding is tagged with an identifier like SC2086, which you can look up on the ShellCheck wiki for a detailed explanation and recommended fix. This makes the tool both a linter and a learning resource.

Why ShellCheck Matters

Shell scripts are notoriously error-prone. Word splitting, glob expansion, and implicit type coercion can turn a simple command into a security incident. ShellCheck matters because it:

Installing ShellCheck

ShellCheck is available through most package managers. Pick the one that matches your environment:

# Debian / Ubuntu
sudo apt install shellcheck

# macOS (Homebrew)
brew install shellcheck

# Arch Linux
sudo pacman -S shellcheck

# Fedora
sudo dnf install ShellCheck

# Using Cargo (Rust)
cargo install shellcheck-sarif

# Nix
nix-shell -p shellcheck

Verify the installation with shellcheck --version. If you need the latest features, download the precompiled binary from the GitHub releases page.

Basic Usage

The simplest invocation analyzes a single file and prints warnings to standard output:

shellcheck myscript.sh

You can also pipe scripts directly into ShellCheck, which is useful for inline checks:

echo 'echo $1' | shellcheck -

By default, ShellCheck reports all severity levels except informational. Use the --severity flag to filter:

shellcheck --severity=warning myscript.sh

Valid severity values are error, warning, info, and style.

Configuring ShellCheck with .shellcheckrc

ShellCheck reads configuration from a file named .shellcheckrc. It searches the current directory and each parent directory until it finds one, which makes it easy to scope rules per project. A typical configuration looks like this:

# .shellcheckrc
disable=SC2086,SC2155
enable=SC2034
external-sources=true

The directives are:

Place this file at the root of your repository and commit it so every contributor gets the same ruleset.

Inline Directives

Sometimes a single line needs an exception. ShellCheck supports inline directives that override the global configuration for specific lines or blocks:

#!/usr/bin/env bash

# Disable SC2086 for the next line only
# shellcheck disable=SC2086
echo $USER

# Disable for an entire function
# shellcheck disable=SC2086
greet() {
  echo "Hello, $1"
}

Use # shellcheck disable=SC2086 above a line to suppress a single warning, or above a function or block to suppress it for the whole scope. You can also add an explanatory comment for future readers:

# shellcheck disable=SC2086 # intentional word splitting
files=$(ls *.txt)

Specifying the Shell Dialect

ShellCheck infers the shell from the shebang line, but you can override it with --shell. This is essential when you lint scripts that are sourced rather than executed directly:

shellcheck --shell=bash lib/common.sh

Supported values include sh, bash, dash, ksh, and ash. Choosing the right dialect ensures ShellCheck applies the correct grammar and flags incompatible constructs.

Working with External Sources

When your script sources other files, ShellCheck cannot analyze them by default. Enable external-sources to follow those includes:

shellcheck --external-sources main.sh

Or set it permanently in .shellcheckrc:

external-sources=true

This is particularly useful in projects with shared library files, because ShellCheck can then validate variable usage and function definitions across the entire codebase.

Output Formats

ShellCheck supports multiple output formats for integration with other tools. The --format flag controls the style:

# Human-readable (default)
shellcheck --format=tty script.sh

# Checkstyle XML
shellcheck --format=checkstyle script.sh > report.xml

# JSON
shellcheck --format=json script.sh > report.json

# GCC-compatible
shellcheck --format=gcc script.sh

The JSON format is ideal for custom dashboards, while checkstyle integrates with Jenkins and SonarQube. The gcc format works well with editors that already parse compiler output.

Integrating with CI/CD

Running ShellCheck in CI ensures every pull request is linted. Here is a GitHub Actions workflow that fails the build on any warning:

name: Lint

on: [push, pull_request]

jobs:
  shellcheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install ShellCheck
        run: sudo apt-get update && sudo apt-get install -y shellcheck
      - name: Run ShellCheck
        run: shellcheck --severity=warning **/*.sh

For GitLab CI, add a similar job to your .gitlab-ci.yml:

shellcheck:
  image: koalaman/shellcheck:stable
  script:
    - shellcheck --severity=warning $(find . -name '*.sh')

Using the official Docker image keeps your pipeline reproducible and avoids system package drift.

Editor Integration

Most editors have first-class ShellCheck support through plugins:

These plugins read your .shellcheckrc automatically, so project-level configuration applies in the editor as well as in CI.

Best Practices

To get the most out of ShellCheck, follow these practices:

A simple pre-commit hook using ShellCheck looks like this:

#!/usr/bin/env bash
# .git/hooks/pre-commit
set -e
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.sh$')
if [ -n "$files" ]; then
  shellcheck --severity=warning $files
fi

Make the hook executable with chmod +x .git/hooks/pre-commit and every commit will be linted automatically.

Common Warnings and How to Fix Them

Understanding the most frequent ShellCheck warnings helps you write better scripts from the start.

SC2086: Double quote to prevent globbing and word splitting.

# Bad
cp $file $destination

# Good
cp "$file" "$destination"

SC2155: Declare and assign separately to avoid masking return values.

# Bad
local var=$(some_command)

# Good
local var
var=$(some_command)

SC2046: Quote this to prevent word splitting.

# Bad
for f in $(ls); do echo "$f"; done

# Good
for f in *; do echo "$f"; done

SC2034: Variable appears unused.

# Bad
unused_var="hello"

# Good — remove it, or reference it

Each of these warnings points to a real class of bug. Fixing them improves robustness and readability.

Conclusion

ShellCheck is a small tool with an outsized impact on shell script quality. By combining a committed .shellcheckrc, targeted inline directives, explicit shell dialects, and CI integration, you create a safety net that catches mistakes early and teaches better scripting habits along the way. Start with sensible defaults, tighten the severity over time, and treat every warning as an opportunity to learn. Your scripts, your teammates, and your future self will all thank you.

— Ad —

Google AdSense will appear here after approval

← Back to all articles