| name | shell-style |
| description | Enforce shell script style conventions when writing or editing .sh files. Use when creating shell scripts, modifying existing scripts, or reviewing shell code for style and portability. |
Shell Script Style Guide
Apply these conventions when writing or editing shell scripts (.sh files).
Fundamentals
- Shebang:
#!/bin/sh — target POSIX shell, not bash
- Error handling: Always
set -e
- Indentation: Tabs only, never spaces
- Quoting: Always quote variables:
"$var" not $var
- Variables: lowercase with underscores:
my_variable
- Functions: lowercase with underscores:
my_function()
- Constants: uppercase:
VERSION="1.0.0"
- Comments: For non-obvious logic, not for what the code does
- Output: Use
printf instead of echo for portable output (especially with escape sequences)
POSIX Portability
No bashisms. Scripts must work on Linux, macOS, BSD, Git Bash, and WSL.
if [ "$var" = "value" ]; then
if [[ "$var" == "value" ]]; then
command -v git >/dev/null 2>&1
which git >/dev/null 2>&1
var="${var#prefix}"
var=$(echo "$var" | sed 's/^prefix//')
Do
- Use
[ ] for tests (not [[ ]])
- Use
= for string comparison (not ==)
- Use
command -v to check for executables
- Use shell parameter expansion for string manipulation
- Use
printf instead of echo for portable output
Avoid
- Bash-specific syntax:
[[ ]], ==, (( )), <<<, ${var,,}, ${var^^}
which (not POSIX)
echo -e / echo -n (behavior varies across systems)
- Arrays (not available in POSIX sh)
source (use . instead)
function keyword (use name() { ... } instead)
Heredocs Over Quoted Strings
Use heredocs for multi-line strings. Use <<- (with dash) to allow tab indentation.
Sigil naming: end_<name> where <name> describes the content. Common sigils: end_panic, end_template, end_help, end_usage. Never use generic sigils like EOF, EOL, END.
panic 2 <<-end_panic
Error message here
with multiple lines
end_panic
template_config() {
cat <<-end_template
config content here
end_template
}
panic 2 "Error message here
with multiple lines"
Script Structure
Organize with clear section headers:
#!/bin/sh
set -e
VERSION="1.0.0"
usage() { ... }
show_help() { ... }
main() { ... }
main "$@"
For a complete ready-to-use script skeleton, read references/new-script-template.md.
Case Statement Formatting
One-line for simple branches — use when each branch is a single simple command. Align ) and ;; for scannability. Pad shorter labels with spaces:
case "$mode" in
project) echo ".$agent/settings.json" ;;
local) echo ".$agent/settings.local.json" ;;
global) echo "$HOME/.$agent/settings.json" ;;
esac
Multi-line for complex branches — use when any branch has multiple commands. Branch body indented one level. ;; on its own line, aligned with body. Empty branches still get ;;:
case "$type" in
create)
mkdir -p "$(dirname "$file")"
cat "$content" > "$file"
;;
skip)
;;
esac
Color Conventions
Standard semantic color mapping — apply consistently across help text, errors, and status output:
| Semantic name | Color | Use for |
|---|
error | red | Error messages |
success | green | Success messages |
warning | yellow | Warnings |
heading | bold | Section headings |
agent | cyan | Agent names |
flag | purple | Flags/options |
path | yellow | File paths |
command | purple | Command names |
Use the c() helper function: $(c error "Error:"), $(c agent "claude").
For full color definitions, c(), and c_list() implementations, read references/color-system.md.
Common Utilities
String trimming
trim() {
local var="$1"
var="${var#"${var%%[![:space:]]*}"}"
var="${var%"${var##*[![:space:]]}"}"
echo "$var"
}
Error Handling
Use panic() for fatal errors.
panic() implementation
panic() {
local exit_code="$1"
shift
local show_usage=0
local message
if [ "$1" = "show_usage" ]; then
show_usage=1
shift
fi
if [ $# -gt 0 ]; then
message="$*"
else
message=$(cat)
fi
printf "\n$(c error Error:) $(trim "$message")\n" >&2
if [ "$show_usage" -eq 1 ]; then
printf "\n$(usage)\n" >&2
fi
printf "\n" >&2
exit "$exit_code"
}
Usage patterns
panic 2 "File not found: $file"
panic 2 show_usage "Invalid argument: $arg"
panic 2 <<-end_panic
Cannot proceed because:
- Reason 1
- Reason 2
end_panic
User-Facing Scripts
All user-facing scripts must provide:
-h / --help flags
usage() — brief syntax summary
show_help() — detailed help with examples
- Color-coded output using the semantic colors above
For usage() and show_help() template implementations, read references/new-script-template.md.
Argument Parsing Pattern
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) show_help; exit 0 ;;
-v|--verbose) verbose=true; shift ;;
--option) option_value="$2"; shift 2 ;;
-*) panic 2 show_usage "Unknown option: $1" ;;
*) positional_args="$positional_args $1"; shift ;;
esac
done
Exit Codes
0 — Success
1 — General failure (tests failed, operation incomplete)
2 — Invalid arguments or configuration error