Lint Bash and sh Scripts

Find common shell-script mistakes before they hit CI: unquoted expansions, missing set -euo pipefail, useless cat, backticks, pipe-to-while subshell traps, unchecked cd, parse-ls loops and POSIX bashisms.

Try:
Lint report

What this tool does

Paste a bash, sh, dash or zsh script and get a static lint report without running the script. The checker focuses on high-signal shell pitfalls that are easy to miss in review: unquoted variable expansions, missing strict mode, useless cat, legacy backticks, pipe-to-while subshell scope traps, unchecked cd, parsing ls, spaced assignments, risky rm -rf paths, simple block/quote syntax mismatches and POSIX-shell bashisms.

Each finding includes a line number, severity, rule code, message and the source line that triggered it. Use Text report while editing, or JSON for CI when you want summary counts and a machine-readable findings array.

Worked example

Input:

#!/usr/bin/env bash
name=$1
echo Hello $name
for f in $(ls *.txt); do
  cat $f | while read line; do
    echo $line
  done
done

Typical output:

Shell: bash
Findings: 6 (0 errors, 5 warnings, 1 info)

L2 [warning] STRICT-MODE: missing strict mode: set -e, set -u, set -o pipefail
  name=$1
L3 [warning] UNQUOTED-VAR: quote variable expansions to avoid word splitting and globbing
  echo Hello $name
L4 [warning] PARSE-LS: do not parse ls output; glob directly or use find -print0/read -d ''
  for f in $(ls *.txt); do
L5 [info] USELESS-CAT: useless use of cat; redirect the file into the consumer instead
  cat $f | while read line; do
L5 [warning] SUBSHELL-SCOPE: commands after a pipeline run in a subshell in many shells; variables assigned in the loop are lost
  cat $f | while read line; do
L6 [warning] UNQUOTED-VAR: quote variable expansions to avoid word splitting and globbing
  echo $line

The script is never executed; only its text is scanned. Comments, single-quoted strings and here-doc bodies are masked before linting so examples in documentation do not produce findings.

Rule codes

CodeWhat it catches
SYNTAXUnclosed if/for/while/case blocks, unmatched fi/done/esac, and unterminated quotes.
MISSING-SHEBANGA script that does not start with #!.
STRICT-MODEMissing set -e, set -u or set -o pipefail.
UNQUOTED-VARUnquoted $name / ${name} expansions in command text.
USELESS-CAT`cat file
BACKTICKSLegacy command substitution using backticks.
SUBSHELL-SCOPE`producer
UNCHECKED-CDcd path without &&, `
PARSE-LSfor f in $(ls ...), which breaks on whitespace and unusual filenames.
ASSIGN-SPACESVAR = value, which runs a command named VAR instead of assigning.
LEGACY-TESTSingle-bracket [ ... ] tests in bash/zsh scripts, where [[ ... ]] avoids splitting surprises.
RM-RISKrm -rf against an interpolated path.
SH-BASHISMBash-only arrays, [[ ... ]], process substitution or here-strings under sh/dash.

Limits and edge cases

FAQ

Is this a replacement for ShellCheck?

No. It catches a practical subset of common mistakes and runs entirely inside this toolkit, but it is intentionally heuristic. Use it for quick local review and CI-friendly JSON output, and keep a full grammar-aware shell linter in your release pipeline.

Does the tool execute my script?

No. The script is treated as plain text and scanned in Rust/WebAssembly. Commands, command substitutions and variables are not evaluated, no files are read, and no network calls are made.

How do I hide a finding I have reviewed?

Put the rule code in Ignore rule codes, separated by commas or spaces. For example, LEGACY-TEST USELESS-CAT hides those two rule families while still reporting syntax errors, strict-mode gaps and other warnings.

Why does POSIX sh report different findings from bash?

Bash and zsh accept features such as arrays, [[ ... ]], process substitution and here-strings. sh and dash do not, so choosing a POSIX dialect enables SH-BASHISM and disables the bash-specific LEGACY-TEST hint.

Can I use it in CI?

Yes. Choose JSON for CI or pass format=json on the CLI. The output includes total, error, warning and info counts plus a findings array with line, severity, code, message and source fields.

Developer & Automation Access

Run it from the terminal

Same engine as this page, headless — via the gizza CLI:

gizza tool shell-script-linter '#!/usr/bin/env bash
for f in $(ls *.txt); do
  cat $f | while read line; do
    echo $line
  done
done'

New to the CLI? Get gizza →

Open it by URL

Pre-fill and auto-run this tool with query parameters — the names match the API/CLI:

https://gizza.ai/tools/shell-script-linter/?script=%23%21%2Fusr%2Fbin%2Fenv%20bash%0Afor%20f%20in%20%24%28ls%20%2A.txt%29%3B%20do%0A%20%20cat%20%24f%20%7C%20while%20read%20line%3B%20do%0A%20%20%20%20echo%20%24line%0A%20%20done%0Adone&shell=auto&min_severity=all&ignore=LEGACY-TEST%2C%20USELESS-CAT&format=text

Machine-readable descriptor: tool.json — title + parameters JSON Schema for agents.