From shellcheck
Provides best practices for Bash/shell scripts: shebang with safety options, variable quoting and defaults, conditionals, loops, arrays. Use when writing or modifying scripts.
npx claudepluginhub thebushidocollective/han --plugin shellcheckThis skill is limited to using the following tools:
Core patterns and best practices for writing robust, maintainable shell scripts.
Enforces modern best practices for shell scripts: portable shebangs, strict mode, quoted variables, error traps, exit codes, and secure coding. Use when writing Bash or POSIX sh scripts.
Provides core patterns and best practices for Bash 5.1+ script development: set -euo pipefail, trap error handling, argument parsing, variable usage, pure-bash string/array ops, and file operations.
Writes and reviews defensive Bash scripts for production automation, CI/CD pipelines, system utilities, with strict error handling, portability, testing via Bats, and ShellCheck linting.
Share bugs, ideas, or general feedback.
Core patterns and best practices for writing robust, maintainable shell scripts.
Always start scripts with a proper shebang and safety options:
#!/usr/bin/env bash
set -euo pipefail
# Script description here
set -e: Exit on any command failureset -u: Error on undefined variablesset -o pipefail: Pipeline fails if any command fails# No spaces around =
name="value"
# readonly for constants
readonly CONFIG_DIR="/etc/myapp"
# local in functions
my_function() {
local result="computed"
echo "$result"
}
# Good - prevents word splitting and glob expansion
echo "$variable"
cp "$source" "$destination"
# Bad - can break on spaces or special characters
echo $variable
cp $source $destination
# Use default if unset
name="${NAME:-default}"
# Use default if unset or empty
name="${NAME:-}"
# Assign default if unset
: "${NAME:=default}"
# Error if unset
: "${REQUIRED_VAR:?Error: REQUIRED_VAR must be set}"
# Modern syntax - preferred
if [[ -f "$file" ]]; then
echo "File exists"
fi
# String comparison
if [[ "$string" == "value" ]]; then
echo "Match"
fi
# Numeric comparison
if (( count > 10 )); then
echo "Greater than 10"
fi
# Regex matching
if [[ "$input" =~ ^[0-9]+$ ]]; then
echo "Numeric input"
fi
| Operator | Description |
|---|---|
-f | File exists and is regular file |
-d | Directory exists |
-e | Path exists |
-r | Readable |
-w | Writable |
-x | Executable |
-z | String is empty |
-n | String is not empty |
# Iterate over list
for item in one two three; do
echo "$item"
done
# Iterate over files (use glob, not ls)
for file in *.txt; do
[[ -e "$file" ]] || continue # Handle no matches
process "$file"
done
# C-style for loop
for (( i = 0; i < 10; i++ )); do
echo "$i"
done
# Read lines from file
while IFS= read -r line; do
echo "$line"
done < "$filename"
# Read with process substitution
while IFS= read -r line; do
echo "$line"
done < <(some_command)
# Declare array
declare -a files=()
# Add elements
files+=("file1.txt")
files+=("file2.txt")
# Iterate all elements
for file in "${files[@]}"; do
echo "$file"
done
# Get array length
echo "${#files[@]}"
# Access by index
echo "${files[0]}"
# Modern syntax - preferred
result=$(command)
# Nested substitution
result=$(echo $(date))
# Avoid legacy backticks
result=`command` # Don't use this
# Function definition
process_file() {
local file="$1"
local output_dir="${2:-./output}"
if [[ ! -f "$file" ]]; then
echo "Error: File not found: $file" >&2
return 1
fi
# Process the file
cp "$file" "$output_dir/"
}
# Call with arguments
process_file "input.txt" "/tmp/output"
#!/usr/bin/env bash for portabilityset -euo pipefail[[ ]] instead of [ ] for tests$(command) instead of backtickscommand -v cmd >/dev/null