shell-scripting
Skill169 starsChanged 8 days agoApache-2.0View on GitHub
Practical bash scripting guidance emphasising defensive programming, ShellCheck compliance, and simplicity. Use when writing shell scripts that need to be reliable and maintainable.
- Reads credentials
- Deletes or force-pushes
12 other projects use a version of this.
Adopted by
Most recently taken up by
- DAAF-Contribution-Community/daaftheir own version, first seen Nov 3, 2026
- iuliandita/skillstheir own version, first seen Oct 14, 2026
- Abdox-menara/guard-skillstheir own version, first seen Aug 24, 2026
- 2867203296/hermes-agenttheir own version, first seen Aug 19, 2026
- AntheaLaffy/missing-semester-skills-dshtheir own version, first seen Aug 16, 2026
- AntheaLaffy/the-missing-semester-skillstheir own version, first seen Aug 16, 2026
Used with
Projects with it also use ci-cd and testing.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
JohnJBorenSkillci-cd
CI/CD pipeline design, GitHub Actions, testing automation, and deployment strategies
Also in 3 projects that use this
NVIDIASkilltesting
Run Python and Rust tests for physicsnemo-curator using uv + pytest with coverage reporting via pytest-cov, and cargo-nextest for Rust tests. Includes benchmark workflows with pytest-benchmark and criterion.
Also in 3 projects that use this
Other versions12 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 12
- mylee04/code-notify★ 288same name
- DAAF-Contribution-Community/daaf★ 239same name
- iuliandita/skills★ 11same name
- CodingKaiser/kaiser-skills★ 6same name
- ffsshhttiikk/opencode-agents-skills★ 2same name
- yuexueyu/anna-agent-skills★ 2same name
- albertdobmeyer/openskill-forge★ 1same name
- AntheaLaffy/the-missing-semester-skills★ 1same name
- 2867203296/hermes-agent★ 0same name
- Abdox-menara/guard-skills★ 0same name
2 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository71 other files it gives its agents.
- Rules/CLAUDE.mdCLAUDE.md
- Skills/ambitious-model-use/CLAUDE.mdCLAUDE.md
- Skills/authoring-claude-md/CLAUDE.mdCLAUDE.md
- Skills/bento-slides/CLAUDE.mdCLAUDE.md
- Skills/extract-wisdom/CLAUDE.mdCLAUDE.md
- Skills/ghostty-config/CLAUDE.mdCLAUDE.md
- Skills/llm-wiki/CLAUDE.mdCLAUDE.md
- Skills/llm-wiki/examples/CLAUDE.mdCLAUDE.md
- Skills/openspec-bootstrap/CLAUDE.mdCLAUDE.md
- Skills/openspeccer/CLAUDE.mdCLAUDE.md
- Skills/rewrite-slop/CLAUDE.mdCLAUDE.md
- Skills/skill-creator-primer/CLAUDE.mdCLAUDE.md
- Skills/yue2-music/CLAUDE.mdCLAUDE.md
- ai-changelogSkill
- ambitious-model-useSkill
- analyse-designSkill
- apply-mantel-stylesSkill
- authoring-claude-mdSkill
- backlogSkill
- bento-slidesSkill
- code-reviewSkill
- code-simplificationSkill
- creating-development-plansSkill
- critical-thinking-logical-reasoningSkill
- deeperSkill
- deferred-task-executionSkill
- demo-recorderSkill
- excalidraw-diagramsSkill
- explainSkill
- extract-wisdomSkill
- find-docsSkill
- ghostty-configSkill
- githubSkill
- golang-developmentSkill
- graphifySkill
- grill-meSkill
- guided-demoSkill
- handoffSkill
- hardware-designSkill
- herdrSkill
- home-assistantSkill
- html-design-examplesSkill
- improve-codebase-architectureSkill
- json-schema-lookupSkill
- liteparseSkill
- llm-prompting-guideSkill
- ml-llm-wikiSkill
- llm-wikiSkill
- mermaid-diagramsSkill
- openspec-bootstrapSkill
- openspeccerSkill
- pptx-to-mdSkill
- private-software-distributionSkill
- prompt-enhancerSkill
- prototypeSkill
- python-developmentSkill
- raspberry-pi-pico2Skill
- release-debriefSkill
- retroSkill
- rewrite-slopSkill
- The first 60 shown.
The file
Show its rules and text
We split this skill into the rules it gives an agent
20 rules. 0 are common, 17 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 6 edited versions we read keep these.
17
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 24
When you need proper data structures beyond arrays
- Distinctive: line 26
If a script passes ~100 lines or gets complex, offer to rewrite it in Python or Go before it becomes unmaintainable.
- Distinctive: line 30
Every bash script must have these elements:
- Very specific: line 49
When to add
-x: Only for debugging, not in production scripts (makes output noisy). - Distinctive: line 52
Run ShellCheck on EVERY script before committing:
- Distinctive: line 123
Use this for straightforward scripts:
- Distinctive: line 149
Use trap for guaranteed cleanup:
- Very specific: line 217
Use [[ ]] for bash (safer and more features):
- Distinctive: line 267
Avoid creating temporary files when possible:
- Distinctive: line 305
Keep logging simple and consistent:
- Distinctive: line 372
Don't pipe to while (creates subshell):
- Distinctive: line 399
Long strings: Use here-documents or embedded newlines
- Very specific: line 437
Libraries: Always .sh extension and NOT executable
- Distinctive: line 441
Document functions that aren't obvious:
- Distinctive: line 511
If your script has any of these, consider rewriting in Python/Go:
- Distinctive: line 546
Before considering a bash script complete:
- Distinctive: line 564
Start simple: Don't over-engineer. Most scripts should be <50 lines.
- Distinctive: line 568
Know when to stop: If it's getting complex, use a real language.
- Distinctive: line 569
Compose don't complicate: Use pipes and process substitution.
- Distinctive: line 571
Test error paths: Make sure your script fails safely.
“Common” is measured: kept by at least 6 in 10 of the 6 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Bash Scripting Best Practices
Guidance for writing reliable, maintainable bash scripts following modern best practices. Emphasises simplicity, automated tooling, and defensive programming without over-engineering.
When to Use Shell (and When Not To)
Use Shell For:
- Small utilities and simple wrapper scripts (<100 lines)
- Orchestrating other programmes and tools
- Simple automation tasks
- Build/deployment scripts with straightforward logic
- Quick data transformation pipelines
Do NOT Use Shell For:
- Complex business logic or data structures
- Performance-critical code
- Scripts requiring extensive error handling
- Anything over ~100 lines or with non-straightforward control flow
- When you need proper data structures beyond arrays
If a script passes ~100 lines or gets complex, offer to rewrite it in Python or Go before it becomes unmaintainable.
Mandatory Foundations
Every bash script must have these elements:
1. Proper Shebang
#!/usr/bin/env bashWhy: Portable across systems where bash may not be at /bin/bash (e.g., macOS, BSD, NixOS).
Alternative: #!/bin/bash if you know the script only runs on Linux and prefer explicit paths.
2. Strict Mode
set -euo pipefailWhat each flag does:
-e: Exit immediately if any command fails (non-zero exit)-u: Treat unset variables as errors-o pipefail: Pipe fails if ANY command in pipeline fails (not just the last)
When to add -x: Only for debugging, not in production scripts (makes output noisy).
3. ShellCheck Compliance
Run ShellCheck on EVERY script before committing:
shellcheck script.shFix all warnings. ShellCheck catches:
- Unquoted variables
- Deprecated syntax
- Common bugs and pitfalls
- Portability issues
4. Basic Script Structure
#!/usr/bin/env bash
set -euo pipefail
# Brief description of what this script does
# Simple error reporting
die() {
echo "Error: ${1}" >&2
exit 1
}
# Your code hereCore Safety Patterns
Always Quote Variables
Why: Prevents word splitting and globbing disasters.
# Wrong - dangerous
cp $source $destination
rm -rf $prefix/bin
# Correct - safe
cp "${source}" "${destination}"
rm -rf "${prefix}/bin"
# Special case: Always use braces with variables
echo "${var}" # Good
echo "$var" # Acceptable but less consistent
echo $var # Bad - unquotedCheck Required Variables
# Fail fast if required variables aren't set
: "${REQUIRED_VAR:?REQUIRED_VAR must be set}"
# Or with custom message
: "${DATABASE_URL:?DATABASE_URL is required. Set it in .env}"Validate Inputs
# Check file exists before operating on it
[[ -f "${config_file}" ]] || die "Config file not found: ${config_file}"
# Check command exists before using it
command -v jq >/dev/null 2>&1 || die "jq is required but not installed"
# Validate directory before cd
[[ -d "${target_dir}" ]] || die "Directory does not exist: ${target_dir}"Essential Patterns
Pattern 1: Simple Script Template
Use this for straightforward scripts:
#!/usr/bin/env bash
set -euo pipefail
# Description: Process log files and extract errors
die() {
echo "Error: ${1}" >&2
exit 1
}
# Check dependencies
command -v jq >/dev/null 2>&1 || die "jq required"
# Validate arguments
[[ $# -eq 1 ]] || die "Usage: ${0} <logfile>"
logfile="${1}"
[[ -f "${logfile}" ]] || die "File not found: ${logfile}"
# Main logic
grep ERROR "${logfile}" | jq -r '.message'Pattern 2: Cleanup on Exit
Use trap for guaranteed cleanup:
#!/usr/bin/env bash
set -euo pipefail
# Create temp directory and ensure cleanup
tmpdir=$(mktemp -d)
trap 'rm -rf "${tmpdir}"' EXIT
# Now use tmpdir safely - cleanup happens automatically
echo "Working in: ${tmpdir}"Pattern 3: Safe Function Definition
Functions should be simple and focused:
# Good: Simple, single-purpose function
check_dependency() {
local cmd="${1}"
command -v "${cmd}" >/dev/null 2>&1 || die "${cmd} not installed"
}
# Good: Local variables, clear purpose
process_file() {
local file="${1}"
local output="${2}"
[[ -f "${file}" ]] || die "Input file missing: ${file}"
# Do processing
sed 's/foo/bar/g' "${file}" > "${output}"
}Important: Declare and set variables from command substitution separately to catch errors:
# Wrong - hides errors
local result="$(failing_command)"
# Correct - catches errors
local result
result="$(failing_command)" # Will fail properly with set -ePattern 4: Safe Array Handling
Arrays are useful for handling lists with spaces:
# Create array
declare -a files=("file one.txt" "file two.txt" "file three.txt")
# Iterate safely - always quote with [@]
for file in "${files[@]}"; do
echo "Processing: ${file}"
done
# Build command arguments safely
declare -a flags=(--verbose --output "${output_file}")
mycommand "${flags[@]}" "${input}"
# Read command output into array
mapfile -t lines < <(grep pattern "${file}")Pattern 5: Conditional Testing
Use [[ ]] for bash (safer and more features):
# File tests
[[ -f "${file}" ]] # File exists
[[ -d "${dir}" ]] # Directory exists
[[ -r "${file}" ]] # File readable
[[ -w "${file}" ]] # File writable
[[ -x "${binary}" ]] # File executable
# String tests
[[ -z "${var}" ]] # String is empty
[[ -n "${var}" ]] # String is not empty
[[ "${a}" == "${b}" ]] # String equality (use ==, not =)
# Numeric comparison (use (( )) for numbers)
(( count > 0 ))
(( total >= minimum ))
# Combined conditions
[[ -f "${file}" && -r "${file}" ]] || die "File not readable: ${file}"Pattern 6: Simple Argument Handling
For simple scripts, prefer positional arguments:
#!/usr/bin/env bash
set -euo pipefail
# For 1-3 arguments, just use positional parameters
[[ $# -eq 2 ]] || die "Usage: ${0} <source> <dest>"
source="${1}"
dest="${2}"
[[ -f "${source}" ]] || die "Source not found: ${source}"For scripts needing flags, keep it simple:
# Use environment variables instead of complex flag parsing
VERBOSE="${VERBOSE:-false}"
DRY_RUN="${DRY_RUN:-false}"
# Run like: VERBOSE=true DRY_RUN=true ./script.sh input.txtPattern 7: Process Substitution Over Temp Files
Avoid creating temporary files when possible:
# Instead of:
first_command > /tmp/output.txt
second_command < /tmp/output.txt
rm /tmp/output.txt
# Use process substitution:
second_command <(first_command)
# For multiple inputs:
diff <(sort file1.txt) <(sort file2.txt)Pattern 8: Prefer Builtins Over External Commands
Builtins are faster and more reliable:
# Use bash parameter expansion over sed/awk for simple cases
filename="${path##*/}" # basename
dirname="${path%/*}" # dirname
extension="${filename##*.}" # get extension
name="${filename%.*}" # remove extension
# Use (( )) for arithmetic over expr
count=$(( count + 1 )) # Not: count=$(expr ${count} + 1)
# Use [[ ]] over [ ] or test
[[ -f "${file}" ]] # Not: test -f "${file}"
# Use ${#var} for string length
length="${#string}" # Not: length=$(echo "${string}" | wc -c)Intermediate Patterns
Pattern 9: Structured Logging
Keep logging simple and consistent:
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ${1}" >&2
}
error() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: ${1}" >&2
}
# Usage
log "Starting process"
error "Failed to connect to database"Pattern 10: Main Function Pattern
For longer scripts (50+ lines), use a main function:
#!/usr/bin/env bash
set -euo pipefail
setup() {
# Dependency checks, variable initialisation
command -v jq >/dev/null 2>&1 || die "jq required"
}
process() {
# Main logic here
log "Processing data"
}
cleanup() {
# Cleanup if needed
log "Cleanup complete"
}
main() {
setup
process
cleanup
}
# Call main with all script arguments
main "${@}"Pattern 11: Idempotent Operations
Scripts should be safe to run multiple times:
# Check before creating
if [[ ! -d "${target_dir}" ]]; then
mkdir -p "${target_dir}"
fi
# Check before writing config
if [[ ! -f "${config_file}" ]]; then
echo "DEFAULT_VALUE=true" > "${config_file}"
fi
# Use atomic operations
mv "${source}" "${dest}" # Atomic on same filesystemPattern 12: Safe While Loop Reading
Don't pipe to while (creates subshell):
# Wrong - variables modified in subshell are lost
count=0
cat file.txt | while read -r line; do
(( count++ ))
done
echo "${count}" # Will be 0!
# Correct - use process substitution
count=0
while read -r line; do
(( count++ ))
done < <(cat file.txt)
echo "${count}" # Correct count
# Or use mapfile for simple cases
mapfile -t lines <file.txt
count="${#lines[@]}"Style Guidelines
Formatting
- Indentation: 4 spaces, never tabs
- Line length: Maximum 120 characters
- Long strings: Use here-documents or embedded newlines
# Long command - break at logical points
docker run \
--name my-container \
--volume "${PWD}:/data" \
--env "FOO=bar" \
my-image:latest
# Long string - use here-doc
cat <<EOF
This is a long message
that spans multiple lines
and is more readable this way.
EOFNaming Conventions
# Functions: lowercase with underscores
check_dependencies() { ... }
process_files() { ... }
# Local variables: lowercase with underscores
local input_file="${1}"
local line_count=0
# Constants/environment variables: UPPERCASE with underscores
readonly MAX_RETRIES=3
readonly CONFIG_DIR="/etc/myapp"
# Source files: lowercase with underscores
# my_library.shFile Extensions
- Executables:
.shextension OR no extension (prefer no extension for user-facing commands) - Libraries: Always
.shextension and NOT executable
Function Documentation
Document functions that aren't obvious:
# Good: Simple function, no comment needed (name says it all)
check_file_exists() {
[[ -f "${1}" ]]
}
# Good: Complex function, documented
# Processes log files and extracts error messages
# Arguments:
# $1 - Input log file path
# $2 - Output directory
# Returns:
# 0 on success, 1 on failure
process_logs() {
local logfile="${1}"
local output_dir="${2}"
# Implementation
}What to Avoid
Don't Use These
# Don't use backticks - use $()
output=`command` # Old style
output=$(command) # Correct
# Don't use eval - almost always wrong
eval "${user_input}" # Dangerous!
# Don't use expr - use (( ))
result=$(expr 5 + 3) # Old style
result=$(( 5 + 3 )) # Correct
# Don't use [ ] when [[ ]] is available
[ -f "${file}" ] # POSIX compatible, less features
[[ -f "${file}" ]] # Bash, safer and more features
# Don't use $[ ] for arithmetic - deprecated
result=$[5 + 3] # Deprecated
result=$(( 5 + 3 )) # Correct
# Don't use function keyword unnecessarily
function foo() { ... } # Redundant
foo() { ... } # CleanerAnti-Patterns
# Don't glob or split unquoted
rm ${files} # DANGEROUS
rm "${files}" # Safe
# Don't use ls output in scripts
for file in $(ls); do # Breaks with spaces
for file in *; do # Correct
# Don't pipe yes to commands
yes | risky-command # Bypasses important prompts
# Don't ignore error codes
make build # Did it work?
make build || die "Build failed" # BetterComplexity Warning Signs
If your script has any of these, consider rewriting in Python/Go:
- More than 100 lines
- Complex data structures beyond simple arrays
- Nested loops over arrays of arrays
- Heavy string manipulation logic
- Complex state management
- Mathematical calculations beyond basic arithmetic
- Need for unit testing individual functions
- JSON/YAML parsing beyond simple jq queries
Advanced: Dry-Run Pattern
For scripts that modify things:
DRY_RUN="${DRY_RUN:-false}"
run() {
if [[ "${DRY_RUN}" == "true" ]]; then
echo "[DRY RUN] ${*}" >&2
return 0
fi
"${@}"
}
# Usage
run cp "${source}" "${dest}"
run rm -f "${old_file}"
# Run script: DRY_RUN=true ./script.shQuick Reference Checklist
Before considering a bash script complete:
- ShellCheck passes with no warnings
- Has proper shebang (
#!/usr/bin/env bash) - Has strict mode (
set -euo pipefail) - All variables quoted (
"${var}") - Required dependencies checked
- Proper error messages to stderr
- Cleanup trap if using temp files
- Script is idempotent where possible
- Under 100 lines (or has strong justification)
- Uses
command -vnotwhich - Arrays used for lists with spaces
- No
eval,lsparsing, or backticks - Functions have local variables
Summary
- Start simple: Don't over-engineer. Most scripts should be <50 lines.
- Use ShellCheck: It catches most problems automatically.
- Quote everything:
"${var}"not$var. - Fail fast:
set -euo pipefailand validate inputs. - Know when to stop: If it's getting complex, use a real language.
- Compose don't complicate: Use pipes and process substitution.
- Be idempotent: Scripts should be safe to run multiple times.
- Test error paths: Make sure your script fails safely.
Remember: Shell scripts are for gluing things together, not building complex logic. Keep them simple, safe, and focused.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

