# ShellCheck: A Second Pair of Eyes

ShellCheck reads a script without running it and points out likely mistakes, each with its line, a code such as SC2086 and a plain message; the codes worth knowing are an unquoted variable, a cd that might fail and a loop over ls, and a disable comment silences one warning on purpose.

Journeyman · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Run ShellCheck on a script and read each warning, its line, its code and its message
- Fix the problems behind SC2086, SC2164 and SC2045, and silence one warning on purpose with a disable comment

## Key takeaways

- ShellCheck is a free linter for shell scripts: shellcheck script.sh reads the script without running it and lists the lines that are likely mistakes.
- Each warning shows the line, points at the part with ^, and gives a code such as SC2086, a level (error, warning, info or style) and a message.
- SC2086 asks for double quotes around a variable, SC2164 for cd ... || exit, and SC2045 for a wildcard instead of a loop over ls.
- Every code has a page at shellcheck.net/wiki with an example and the fix.
- ### shellcheck disable=SC2086 on the line above silences one warning there. Use it only when you've read the warning and are sure.

Before an essay goes in, a friend reads it and circles the sentence that doesn't say what you meant. Scripts have the same kind of friend. **ShellCheck** is a **linter**, a program that reads your code without running it and points out lines that are likely mistakes. If you write scripts in VS Code with its ShellCheck extension (*Writing Scripts in VS Code*), you've met its wavy underlines. This lesson runs it in the terminal, where it can check any script, on any computer, before the script ever touches a file. Teams that keep scripts in a shared project often run it on every change, so a risky line is caught before anyone runs it.

## A script that looks fine

Here's a mover written in a hurry, `quick-move.sh`, for the `mover` practice folder from *Errors a User Can Act On*. It goes into the folder it's given and moves everything up into `photos`, with `mv -n` so nothing is replaced (*Copy, Move and Rename: cp and mv*). Make it with `nano` and `chmod +x` it:

```bash
#!/usr/bin/env bash
# quick-move.sh: a mover written in a hurry.
cd $1
for file in $(ls); do
  mv -n $file ../photos/
done
```

Run the practice lines for a fresh folder, add a photo with a space in its name, and try it:

```bash
printf 'waves\n' > "inbox/beach day.jpg"
./quick-move.sh inbox
ls inbox
```

```text
mv: cannot stat 'beach': No such file or directory
mv: cannot stat 'day.jpg': No such file or directory
 IMG_0412.jpg  'beach day.jpg'
```

`beach day.jpg` was split into two names that don't exist. (`IMG_0412.jpg` stayed because `mv -n` won't replace the cake, without a word.) Each of the script's lines has a problem, and ShellCheck finds them all without running anything.

## Install ShellCheck and run it

Install it once with your package manager (*Installing Tools: Homebrew and apt*); `sudo` asks for your password:

```bash
sudo apt install shellcheck     # Ubuntu, and Ubuntu in WSL
brew install shellcheck         # a Mac, with Homebrew
```

Then give it a script:

```bash
shellcheck quick-move.sh
```

![A terminal showing ShellCheck's report on quick-move.sh: line 3, cd $1, with warning SC2164 and SC2086 marked under it; line 4, for file in $(ls), with SC2045 marked under $(ls); and line 5, the mv line, with SC2086 marked under $file](https://www.ask-codey.com/lessons/TODO)

ShellCheck prints each line it has something to say about, then a `^` and a row of dashes under the exact part it means, and then:

- the **code**, such as `SC2086`, the same code VS Code shows;
- the **level**: `error` (it will go wrong), `warning` (it can go wrong), `info` or `style` (worth a look);
- a **message** in one sentence.

When it has nothing to say, it prints nothing, and its exit code is 0, so a script can check another script with `shellcheck script.sh && echo "Looks good"`. You can also paste a script into the box at `shellcheck.net` in a browser.

## The codes worth knowing

A handful of codes come up again and again. The messages here are quoted from ShellCheck's wiki:

- **SC2086**, *Double quote to prevent globbing and word splitting.* Write `"$file"` and `"$1"`: quotes around every variable, so a name with a space stays one name.
- **SC2164**, *Use `cd ... || exit` in case `cd` fails.* Write `cd "$1" || exit`, so a wrong folder name stops the script instead of running the rest in the folder you happened to be in.
- **SC2045**, *Iterating over ls output is fragile. Use globs.* Write `for file in *` instead of `for file in $(ls)`.
- **SC2154**, *var is referenced but not assigned.* Fix the variable's name: it catches a typo such as `$photo_dri` before the script runs, where strict mode catches it only when that line runs.
- **SC2162**, *`read` without `-r` will mangle backslashes.* Write `read -r`.

Every code has its own page, with an example and the fix: `https://www.shellcheck.net/wiki/SC2045`, with the code you want at the end. Reading that page is the quickest way to understand a warning you haven't seen before.

## Fix it, or silence one on purpose

Here's `quick-move.sh` with ShellCheck's advice taken: quotes around each variable, `|| exit` after `cd`, and a wildcard instead of `$(ls)`:

```bash
#!/usr/bin/env bash
# quick-move.sh: a mover written in a hurry, then fixed.
cd "$1" || exit
for file in *; do
  mv -n "$file" ../photos/
done
```

Run it on the same folder:

```bash
./quick-move.sh inbox
ls inbox
```

```text
IMG_0412.jpg
```

`beach day.jpg` moved in one piece. (The cake is still safe, and `IMG_0412.jpg` stays put, silently: the full `mover.sh` is the one that says so.)

Once in a while a warning is about something you meant. Then put a comment on the line right above it, naming the code, with a line of your own above that saying why, so the next reader knows it wasn't an accident:

```bash
options="-l -h"
# options holds two options on purpose, so let bash split it into two words.
# shellcheck disable=SC2086
ls $options
```

The comment silences that one code on that one line, and nothing else. Use it rarely: most warnings are right, even when the script happens to work today.

## Check yourself

2 questions to try on the lesson page.

## So what

ShellCheck reads every script you write the way a careful colleague would, and catches quoting, `cd` and loop mistakes before they ever run on real files.

## Key terms

- [ShellCheck](https://www.ask-codey.com/glossary/shellcheck)
- [Linter](https://www.ask-codey.com/glossary/linter)
- [Quoting](https://www.ask-codey.com/glossary/quoting)
- [Glob](https://www.ask-codey.com/glossary/glob)

## Cheat sheets

- [ShellCheck: A Second Pair of Eyes](https://www.ask-codey.com/cheat-sheets/bash-shellcheck-cheat-sheet)

## Related

- [Writing Scripts in VS Code](https://www.ask-codey.com/lessons/bash-scripts-in-vs-code)
- [Debugging Scripts: bash -x](https://www.ask-codey.com/lessons/bash-debugging-scripts)
- [Quotes: Double, Single and None](https://www.ask-codey.com/lessons/bash-quoting)
- [Testing Scripts with bats](https://www.ask-codey.com/lessons/bash-testing-with-bats)

Source: https://www.ask-codey.com/lessons/bash-shellcheck
