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.

how-to4 steps

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

  1. Step 1: Write a script with a quoting bug

    The argument expansion below is deliberately unquoted.

    sh
    trmn_guide_dir="$(mktemp -d)"
    cd "$trmn_guide_dir"
    cat > greet.sh <<'EOF'
    #!/usr/bin/env bash
    printf 'Hello, %s!\n' $1
    EOF
    shellcheck greet.sh
  2. Step 2: Fix the expansion

    ShellCheck reports SC2086. Replace the script with this version, then lint it again.

    sh
    cat > greet.sh <<'EOF'
    #!/usr/bin/env bash
    printf 'Hello, %s!\n' "$1"
    EOF
    shellcheck greet.sh
  3. Step 3: Add behavior tests

    The second test makes sure a wildcard is treated as text instead of expanding to filenames.

    sh
    cat > 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.bats
    output
    1..2
    ok 1 keeps a name with a space together
    ok 2 keeps a wildcard literal
  4. 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.

related