Lint and test a shell script with ShellCheck and Bats
Catch a quoting bug with ShellCheck, then add Bats tests for names containing spaces and wildcard characters.
Updated
in short
ShellCheck identifies unsafe shell constructs; Bats checks what the script actually prints. Use both against the same small script.
Use the small fixture below to check the workflow before applying it to your own project.
Tools used
Before you start
- A Bash or Zsh terminal on Linux or macOS
- The listed tools installed and available on PATH
Steps
Step 1: Write a script with a quoting bug
The argument expansion below is deliberately unquoted.
shtrmn_guide_dir="$(mktemp -d)" cd "$trmn_guide_dir" cat > greet.sh <<'EOF' #!/usr/bin/env bash printf 'Hello, %s!\n' $1 EOF shellcheck greet.shStep 2: Fix the expansion
ShellCheck reports SC2086. Replace the script with this version, then lint it again.
shcat > greet.sh <<'EOF' #!/usr/bin/env bash printf 'Hello, %s!\n' "$1" EOF shellcheck greet.shStep 3: Add behavior tests
The second test makes sure a wildcard is treated as text instead of expanding to filenames.
shcat > greet.bats <<'EOF' #!/usr/bin/env bats @test "keeps a name with a space together" { run bash "$BATS_TEST_DIRNAME/greet.sh" 'Ada Lovelace' [ "$status" -eq 0 ] [ "$output" = 'Hello, Ada Lovelace!' ] } @test "keeps a wildcard literal" { run bash "$BATS_TEST_DIRNAME/greet.sh" '*' [ "$status" -eq 0 ] [ "$output" = 'Hello, *!' ] } EOF bats --formatter tap greet.batsoutput1..2 ok 1 keeps a name with a space together ok 2 keeps a wildcard literal
Step 4: Check the failure path
In the temporary greet.sh only, remove the double quotes around $1 and run Bats again. The space test should fail. Restore the quoted version afterwards.
Further reading
[SC2086 explanation](https://www.shellcheck.net/wiki/SC2086) · [Writing Bats tests](https://bats-core.readthedocs.io/en/stable/writing-tests.html)
FAQ
- Do passing tests replace linting?
- No. Tests exercise chosen inputs; ShellCheck can find problems those inputs do not cover.
- Why use BATS_TEST_DIRNAME?
- It locates the script beside the test file, even when the test runner starts in another directory.