name: coding-shell-scripts description: Guidelines and patterns for writing bash/shell scripts. Use when creating new shell scripts, bin scripts, or bash utilities. Includes script templates, header comments, error handling, and common patterns.
Coding Shell Scripts
Overview
This skill provides templates and patterns for writing bash scripts. Choose the appropriate template based on your needs:
- Simple Script Structure: For straightforward utilities and single-purpose tools
- Full Script Template: For complex scripts needing logging, cleanup handlers, and robust error handling
Simple Script Structure
For straightforward scripts (utilities, single-purpose tools):
#!/usr/bin/env bash
main() {
# Script logic here
local arg="$1"
if [ -z "$arg" ]; then
echo "Usage: $(basename "$0") <argument>" >&2
exit 1
fi
# Do the work
echo "Processing: $arg"
}
main "$@"
Important: Standalone scripts MUST use exit (not return) for error codes, since they run in their own process.
Full Script Template
For more complex scripts with logging, cleanup, and robust error handling:
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
# Normalize TMPDIR (strip trailing slash for consistent path construction)
TMPDIR="${TMPDIR:-/tmp}"
TMPDIR="${TMPDIR%/}"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
#/ Usage:
#/ Description:
#/ Examples:
#/ Options:
#/ --help: Display this help message
usage() { grep '^#/' "$0" | cut -c4- ; exit 0 ; }
expr "$*" : ".*--help" > /dev/null && usage
readonly LOG_FILE="${TMPDIR}/$(basename "$0").log"
_print() {
case "$1" in
black) color="30" ;; red) color="31" ;; green) color="32" ;;
yellow) color="33" ;; blue) color="34" ;; magenta) color="35" ;;
cyan) color="36" ;; white) color="37" ;;
*) echo "Unknown color: $1" >&2; return 1 ;;
esac
shift
while [ "$#" -gt 1 ]; do
case "$1" in
bold) color="${color};1" ;; dim) color="${color};2" ;;
*) echo "Unknown option: $1" >&2; return 1 ;;
esac
shift
done
supported_colors=$(tput colors 2>/dev/null || echo 0)
if [ -n "$supported_colors" ] && [ "$supported_colors" -gt 8 ]; then
printf "\\033[${color}m%b\\033[0m\\n" "$1"
else
printf "%b\n" "$1"
fi
}
info() { _print cyan "info $@" | tee -a "$LOG_FILE" >&2 ; }
warning() { _print yellow "warning $@" | tee -a "$LOG_FILE" >&2 ; }
error() { _print red "error $@" | tee -a "$LOG_FILE" >&2 ; }
fatal() { _print red bold "fatal $@" | tee -a "$LOG_FILE" >&2 ; exit 1 ; }
cleanup() {
# Remove temporary files
# Restart services
# ...
}
if [[ "${BASH_SOURCE[0]}" = "$0" ]]; then
trap cleanup EXIT
# Script goes here
# ...
fi
Script Validation
CRITICAL: Always validate shell scripts after creating or editing them.
Required Validation Steps
After creating or editing any shell script, you MUST run both validation tools:
-
Syntax Check with bash -n
bash -n script.shCatches basic syntax errors:
- Missing closing quotes
- Unmatched brackets or braces
- Invalid command syntax
-
Static Analysis with shellcheck
shellcheck script.shCatches deeper issues:
- Quoting problems (word splitting, glob expansion)
- Deprecated or unsafe syntax
- Common bugs and anti-patterns
- Unused or misspelled variables
- Portability issues
Validation Pattern
Run both checks together:
bash -n script.sh && shellcheck script.sh
Only consider the script complete after both tools pass without errors.
Handling Shellcheck Warnings
When shellcheck warnings are intentional, disable them with directives:
# Disable specific warning for one line
# shellcheck disable=SC2086
echo $unquoted_on_purpose
# Disable multiple codes for entire file (at top)
# shellcheck disable=SC2086,SC2181
# Document why you're disabling
# We want word splitting here for argument passing
# shellcheck disable=SC2086
command $args
Common codes you might need to disable:
SC2086: Word splitting/globbing (when intentional for argument passing)SC2181: Testing$?explicitly (when needed for clarity)SC1090: Dynamic source paths (shellcheck can't follow them)SC2034: Unused variables (e.g., when reading into multiple vars)
Important: Only disable shellcheck warnings when you understand why they're triggered and have a valid reason to ignore them. Document your reasoning in comments.
Bash Constructs
for
for i in ( ls some-directory ); do
echo $i
done
SomeArray=("foo" "bar" "baz")
for item in ${SomeArray[*]}; do
echo $item
done
case
case "$variable" in
abc) echo "\$variable = abc" ;;
xyz) echo "\$variable = xyz" ;;
esac
ln
ln -s <real_file> <future_link>
select
select result in Yes No Cancel
do
echo $result
done
tput
tput cuu1 # Move cursor up by one line
tput el # Clear the line
Common Patterns
Ternary expressions
<expression> && <on-true expression> || <on-false expression>
# See below example for file/stdin to variable.
File or stdin assignment
Assign either a file or stdin (piped input) to a variable, with fallbacks:
# Use a filepath from args when available, or use stdin
[ $# -ge 1 -a -f "$1" ] && input="$1" || input="-"
content=$(cat $input)
# Use stdin if pipe is occupied, otherwise use a file
(test -s /dev/stdin) && input="-" || input="./config.json"
content=(cat $input)
Arithmetic Operations
Use (()) for arithmetic:
echo $(( 5 - 1 ))
CLI Arguments
More involved: https://medium.com/@Drew_Stokes/bash-argument-parsing-54f3b81a6a8f
# Simple double dash arg
arg_no_bump="false"
expr "$*" : ".*--no-bump" > /dev/null && arg_no_bump="true"
Reference Links
http://tldp.org/LDP/abs/html/index.html
https://stackoverflow.com/documentation/bash/topics
https://dev.to/thiht/shell-scripts-matter