| name | shell-scripting |
| description | Shell script conventions, defensive patterns, and correctness rules: strict mode, quoting, portability, error handling, and common pitfalls. Invoke whenever task involves any interaction with shell scripts — writing, reviewing, debugging, or understanding .sh, .bash, .zsh files. |
Shell Scripting
Write defensively. Shell defaults are hostile — unquoted variables split, unset variables vanish silently, failed
commands continue. Every rule here exists to counteract a specific shell default that causes bugs.
References
Extended examples, code patterns, and lookup tables for the rules below.
- Strict mode, error handling, traps, debugging — [
${CLAUDE_SKILL_DIR}/references/strict-mode.md]: errexit
caveats, pipefail examples, trap patterns, temp file safety, debugging techniques
- Quoting rules, word splitting, globbing — [
${CLAUDE_SKILL_DIR}/references/quoting.md]: Three quoting mechanisms,
"$@" vs "$*", array expansion, printf vs echo, nested quoting
- POSIX sh vs bash, portable constructs — [
${CLAUDE_SKILL_DIR}/references/portability.md]: Feature comparison, GNU
vs BSD tool differences, portable pattern catalog
- Argument parsing, getopts, validation — [
${CLAUDE_SKILL_DIR}/references/arguments.md]: getopts template, manual
long-option parsing, validation patterns, usage messages, stdin detection
- Common shell scripting mistakes — [
${CLAUDE_SKILL_DIR}/references/pitfalls.md]: Iteration pitfalls, variable
pitfalls, test pitfalls, pipeline pitfalls, arithmetic traps
- Pure bash/sh alternatives to external commands — [
${CLAUDE_SKILL_DIR}/references/builtins.md]: Parameter
expansion, replacing sed/cut/basename/expr, arrays, read patterns, arithmetic
Script Header
Every bash script starts with:
#!/usr/bin/env bash
set -euo pipefail
- Shebang: Use
#!/usr/bin/env bash — not #!/bin/bash. The env lookup is more portable across systems where
bash is not at /bin/bash.
set -e (errexit): Exit on command failure. Understand the exceptions: commands in if/while conditions, left
side of &&/||, and negated commands (!) do not trigger errexit.
set -u (nounset): Error on unset variables. Use ${VAR:-default} for optional variables.
set -o pipefail: Pipeline returns the rightmost failing command's exit code, not the last command's.
- For POSIX sh scripts: Use
#!/bin/sh. Drop pipefail (not POSIX). Use set -eu with caution — set -e behavior
varies across sh implementations.
- File header comment: After the shebang, add a brief description of what the script does.
#!/usr/bin/env bash
set -euo pipefail
Quoting
Quoting is the single most important discipline. Unquoted variables undergo word splitting (breaks on IFS characters)
and pathname expansion (glob characters match filenames). Both are silent and devastating.
Core Rules
- Always double-quote variable expansions:
"$var", "${var}".
- Always double-quote command substitutions:
"$(command)".
- Use
"$@" to pass arguments through. Never $* or $@ unquoted. "$@" preserves each argument as a separate
word. "$*" joins them.
- Quote array expansions:
"${arr[@]}" expands each element as a separate word. Unquoted ${arr[@]} undergoes word
splitting.
- Leave globs unquoted:
for f in *.txt — the glob must expand. But always quote variables inside the loop: "$f".
- Leave
[[ ]] right-hand patterns unquoted when doing glob or regex matching. Quote the right side for literal
string comparison.
- Use single quotes for literal strings that need no expansion:
grep 'pattern' file.
- Use
printf instead of echo for data output. echo interprets -n, -e as options on some platforms.
printf '%s\n' "$var" is always safe.
When Quoting Is Not Needed
- Right side of assignment:
var=$other (no splitting in assignment context)
- Inside
(( )) arithmetic: (( x + y ))
- Inside
[[ ]] on the left side: [[ $var == pattern ]]
- Integer special variables:
$?, $#, $$ (guaranteed no spaces)
case word: case $var in ...
Variable Handling
- Naming: lowercase with underscores for local variables (
file_path, line_count). UPPER_CASE for
exported/environment variables and constants (PATH, MAX_RETRIES).
- Declare constants with
readonly:
readonly CONFIG_DIR="/etc/myapp"
- Use
local in functions to prevent variable leakage into global scope. Declare and assign on separate lines when
capturing command output:
local result
result=$(some_command)
Combined local result=$(cmd) masks the exit code — local always returns 0.
- Default values: Use
${VAR:-default} to provide defaults without modifying the variable. Use ${VAR:=default} to
set and use.
- Required variables: Use
${VAR:?error message} to abort if unset.
- Arrays for lists: Use bash arrays instead of space-delimited strings.
files=("file one.txt" "file two.txt")
command "${files[@]}"
Error Handling
Functions
Control Flow
Conditionals
Loops
- Never parse
ls output. Use globs:
for f in ./*.txt; do
[[ -e "$f" ]] || continue
process "$f"
done
- Use
while read for line-oriented input:
while IFS= read -r line; do
printf '%s\n' "$line"
done < file
The IFS= prevents leading/trailing whitespace trimming. The -r prevents backslash interpretation.
- Use process substitution to avoid subshell variable loss:
while IFS= read -r line; do
(( count++ ))
done < <(command)
echo "$count"
- Use
find -print0 with read -d '' for filenames with special characters:
while IFS= read -r -d '' file; do
process "$file"
done < <(find . -type f -print0)
Case Statements
Input Handling
Formatting
Portability
- Choose your target. Decide upfront whether you need POSIX sh compatibility or can require bash.
- If targeting bash: use
#!/usr/bin/env bash, use [[ ]], arrays, and process substitution freely. Specify
minimum bash version if using 4.0+ features (associative arrays, mapfile, case modification).
- If targeting POSIX sh: use
#!/bin/sh, use [ ] with quoted variables, no arrays, no [[ ]], no (( )), no
local (technically non-POSIX but widely supported), no process substitution.
- macOS ships bash 3.2 permanently. If targeting macOS without requiring Homebrew bash, avoid bash 4+ features.
- Avoid GNU-specific tool options when portability matters:
sed -i, grep -P, GNU date flags. Document the
dependency when GNU tools are required.
- Use
command -v to check if a program is available — not which (which is not a builtin and behaves differently
across systems).
ShellCheck Integration
Application
When writing shell scripts: Apply all rules silently. Produce clean, defensive code. Use strict mode, quote
everything, handle errors, use arrays for lists.
When reviewing shell scripts: Cite the specific rule violated. Show the fix inline. Prioritize: quoting bugs > error
handling gaps > style issues.
Integration
the-coder provides the overall coding workflow (discover, plan, verify)
- Language plugins (golang, javascript) handle language-specific tooling
- This skill handles shell-specific correctness and defensive patterns
Quote everything. Handle every error. Trust nothing.