Workflow for creating and modifying shell scripts (bash, zsh, sh) with mandatory shellcheck validation. Use this skill whenever creating a new shell script, editing an existing .sh/.zsh file, modifying files in bin/ or roles/zsh/, working on dotfiles shell configuration, or when the user mentions shellcheck, shell linting, or bash scripting. Also trigger when editing any file that starts with a shebang (#!/bin/bash, #!/bin/sh, #!/usr/bin/env bash, #!/bin/zsh).
A workflow for writing and modifying shell scripts that ensures every change is validated with shellcheck before considering the task complete. The goal is to catch common pitfalls — quoting issues, undefined variables, portability problems — before they cause real trouble.
Before making changes, read the entire file to understand:
#!/bin/bash, #!/bin/sh, #!/bin/zsh, #!/usr/bin/env bash). This determines which shellcheck dialect to use.source or . commands — these affect variable scope and shellcheck findings.If creating a new script:
#!/usr/bin/env bash for portability unless there's a reason to use something else.chmod +x <file>.Write clean, idiomatic shell code:
"$var" not $var (unless intentionally word-splitting).[[ ]] over [ ] in bash/zsh for conditionals — it handles empty strings and pattern matching more safely.$() over backticks for command substitution.set -euo pipefail at the top of bash scripts when appropriate (it makes scripts fail fast on errors, undefined variables, and pipe failures).${var} behaves differently than in bash.After every modification, run shellcheck on the file. This is not optional — always validate before finishing.
Detect the shell type and choose flags automatically:
For bash/sh scripts (shebang contains bash or sh):
shellcheck -s bash <file>
For zsh files (.zsh extension, or shebang contains zsh, or files in roles/zsh/):
shellcheck -s bash --exclude=SC1091,SC2034 <file>
Zsh exclusions explained:
ZSH_THEME, plugins, etc.) that shellcheck can't see being consumed.For files without a clear shebang (e.g., sourced config fragments):
roles/zsh/ or has a .zsh extension, treat it as zsh.shellcheck -s bash.If shellcheck reports issues:
Fix each warning. Most common ones:
$variable."$(command)".read without -r): Use read -r to prevent backslash interpretation.#!/usr/bin/env bash at the top.local var=$(cmd) into local var; var=$(cmd).Re-run shellcheck after fixes to confirm they're clean.
If a warning is a genuine false positive for the specific context, suppress it with an inline directive right above the offending line:
# shellcheck disable=SC2034
MY_VAR="used by external framework"
Always add a comment explaining why the suppression is justified. Do not blanket-disable warnings — suppress only the specific code on the specific line.
The task is done only when shellcheck exits with no warnings (exit code 0). If there are remaining warnings that cannot be fixed, explain each one to the user and get their approval before considering the task complete.
When editing zsh functions in roles/zsh/functions.zsh or roles/zsh/functions/:
[ ] vs [[ ]]: [ "$x" == "y" ] fails in zsh with = not found. Always use [[ "$x" == "y" ]] in zsh files.return 1 explicitly — don't let them fall through with an implicit 0.roles/zsh/functions/ contains standalone files; _-prefixed files are zsh completions (loaded via fpath), others are sourced at shell startup by functions.zsh.zsh -i -c 'funcname args; echo "exit: $?"'| Signal | Dialect | Shellcheck flags |
|---|---|---|
#!/bin/bash or #!/usr/bin/env bash | bash | -s bash |
#!/bin/sh | POSIX sh | -s sh |
#!/bin/zsh or #!/usr/bin/env zsh | zsh | -s bash --exclude=SC1091,SC2034 |
.zsh extension | zsh | -s bash --exclude=SC1091,SC2034 |
File in roles/zsh/ | zsh | -s bash --exclude=SC1091,SC2034 |
| No shebang, no extension | bash (default) | -s bash |