Shell Formatting with shfmt and ShellCheck
Connect shfmt and ShellCheck and use an unquoted variable to compare formatting, static analysis, and runtime checks.
Sources checked on 2026-09-06. Apply the examples sequentially in a separate format-lab project. Commands run from its root on macOS, Linux, or WSL. Merge the relevant keys into existing configuration files.
Correct layout does not prevent word splitting
shfmt formats Shell syntax; ShellCheck finds problems such as missing quotes. This lab assumes every scripts/*.sh file uses Bash. Mixed repositories need explicit dialect boundaries and appropriate shebangs.
Prepare the Go SDK and ShellCheck. Install ShellCheck through your platform, for example brew install shellcheck on macOS or sudo apt-get install shellcheck on Ubuntu. ShellCheck installation
go install mvdan.cc/sh/v3/cmd/shfmt@v3.12.0
shfmt --version
shellcheck --version
code --install-extension foxundermoon.shell-format
code --install-extension timonwong.shellcheck
command -v shfmtThe Go-installed binary normally goes to $(go env GOPATH)/bin, unless GOBIN overrides it. Add that directory to PATH. If VS Code sees a different PATH, set shellformat.path to the absolute path printed by command -v shfmt. Keep personal paths in local settings.
Match editor and CLI options
{
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"shellformat.flag": "-ln bash -i 2",
"shellformat.useEditorConfig": false,
"shellformat.effectLanguages": ["shellscript"]
}This lab explicitly uses -ln bash -i 2 in both places. shfmt also supports EditorConfig. To adopt it later, enable the extension's EditorConfig mode, remove conflicting flags, and verify the output again. shfmt options, shell-format configuration
Compare actual program output
Create scripts/greet.sh:
#!/usr/bin/env bash
name="Ada Lovelace"
if [ -n "$name" ]; then
printf '%s\n' $name
fiRun each command separately. The diff command fails when changes are needed; the write command changes the file.
shfmt -ln bash -i 2 -d scripts/greet.sh
shfmt -ln bash -i 2 -w scripts/greet.sh
shellcheck scripts/greet.sh
bash -n scripts/greet.sh
bash scripts/greet.shFormatting fixes indentation but leaves the expansion unquoted. ShellCheck reports SC2086. Execution prints Ada and Lovelace on separate lines, while bash -n passes because the syntax is legal.
Quote the expansion to pass the name as one argument:
#!/usr/bin/env bash
name="Ada Lovelace"
if [ -n "$name" ]; then
printf '%s\n' "$name"
fiThe result is one line: Ada Lovelace. A fixture containing a space exposes the bug. Understand the input contract before suppressing a warning.
Verify scope and editor diagnostics
shfmt -ln bash -i 2 -d scripts
shellcheck scripts/*.sh
bash -n scripts/greet.shThe glob checks only immediate .sh children. Expand the command when adding nested scripts. Inspect targets before recursively formatting vendor or generated code.
Inspect the ShellCheck diagnostic provider and binary in VS Code. Selecting shfmt as formatter does not install ShellCheck. Remote windows need the tools and extensions in the remote environment. ShellCheck extension
Finish when formatting produces no diff, lint and syntax checks pass, and the spaced name prints on one line.