shell-scripts
Skill0 starsChanged 11 days agoMITView on GitHub
Use when writing, reviewing, or debugging shell scripts, one-liners, Makefile recipes, Dockerfile RUN lines, CI run steps, or git hooks, or when a command behaves differently on macOS and Linux. Covers quoting, strict-mode traps, guarded deletes, cleanup traps, GNU vs BSD tools (sed -i, date, stat), bash 3.2, dash, zsh, and ShellCheck.
- Pipes a download into a shell
- Deletes or force-pushes
3 other projects use a version of this.
Adopted by
Most recently taken up by
- hiromaily/go-crypto-wallettheir own version, first seen Oct 9, 2026
- masonhuemmer/dopstheir own version, first seen Mar 24, 2026
- rundops/dopstheir own version, first seen Mar 24, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- hiromaily/go-crypto-wallet★ 128same name
- masonhuemmer/dops★ 3same name
- rundops/dops★ 3same name
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 repository8 other files it gives its agents.
- code-reviewSkill
- database-migrationsSkill
- debuggingSkill
- dependenciesSkill
- git-workflowSkill
- pull-requestsSkill
- secure-codingSkill
- testingSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 20
Choose the shell on purpose: Use #!/usr/bin/env bash for bash features and #!/bin/sh only for strictly POSIX code (on Debian and Ubuntu, …
- Very specific: line 23
Lint what you write: Run ShellCheck on every script you create or change: shellcheck -x <script>, or without installing it, docker run --rm …
- Distinctive: line 109
Prefer several clear commands to one clever pipeline: when a mistake would be costly.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Shell Scripts
Shell makes the dangerous thing short: an unset variable turns rm -rf "$dir/" into rm -rf /, an unquoted file name with a space becomes two arguments, and a failed command in the middle of a pipeline goes unnoticed. Write scripts that stop on errors, quote everything, guard every delete, and behave the same on the machines they'll run on.
Rules (non-negotiable)
- Quote every expansion:
"$var","$(cmd)","${array[@]}". Leave one unquoted only when you want word splitting or globbing, and say so in a comment. - Guard destructive commands. Before
rm -rf,mv,find -delete, or an overwriting redirect, make sure the path variable is set and sane:rm -rf -- "${build_dir:?}/", plus a check that it isn't/,$HOME, or outside the directory the script owns. Put--before file arguments. For user data, prefer moving to a backup location over deleting. - Fail loudly. Bash scripts start with
set -euo pipefail, knowing where it doesn't protect you ("Strict mode traps"). Errors go to stderr with a clear message and a nonzero exit code. - Keep secrets off command lines and out of traces. Arguments are visible in
ps, shell history, and CI logs, andset -xprints them. Read secrets from environment variables or files, and neverset -xaround them. - Choose the shell on purpose. Use
#!/usr/bin/env bashfor bash features and#!/bin/shonly for strictly POSIX code (on Debian and Ubuntu,/bin/shis dash). Scripts that must run on stock macOS can't use bash 4+ features:/bin/bashthere is 3.2. - Use portable flags, or branch on the OS. GNU and BSD tools differ (
sed -i,date -d,stat -c,grep -P,readlink -f,mktemp -t). Check references/portability.md before using a flag in a script that runs on both. - Never wait for input. Scripts used by automation take flags, not prompts. Only pass
--yes,-f, or--forceto a destructive tool when the user approved that exact operation. - Lint what you write. Run ShellCheck on every script you create or change:
shellcheck -x <script>, or without installing it,docker run --rm -v "$PWD:/mnt:ro" -w /mnt koalaman/shellcheck:stable -x <script>. Fix the findings or explain each one you keep. - Make scripts that change things safe to rerun. Check before acting (does the directory exist, is the line already in the file), offer
--dry-runfor anything destructive or slow, and ask before running a script that deletes data, usessudo, or changes system configuration. Never pipe a download straight into a shell (curl ... | sh) without the user's approval; download, inspect, and verify a checksum first.
Script skeleton
#!/usr/bin/env bash
# Compress *.log files older than 7 days in a directory.
set -euo pipefail
usage() {
cat <<'EOF'
Usage: rotate-logs.sh [--dry-run] <log-dir>
Compress *.log files older than 7 days in <log-dir>.
EOF
}
die() { printf 'rotate-logs: %s\n' "$*" >&2; exit 1; }
dry_run=0
while [ $# -gt 0 ]; do
case $1 in
--dry-run) dry_run=1 ;;
-h | --help) usage; exit 0 ;;
--) shift; break ;;
-*) die "unknown option: $1" ;;
*) break ;;
esac
shift
done
[ $# -eq 1 ] || { usage >&2; exit 2; }
log_dir=$1
[ -d "$log_dir" ] || die "not a directory: $log_dir"
command -v gzip >/dev/null 2>&1 || die "gzip is required"
run() {
if [ "$dry_run" -eq 1 ]; then
printf 'would run:'
printf ' %q' "$@"
printf '\n'
else
"$@"
fi
}
find "$log_dir" -type f -name '*.log' -mtime +7 -print0 |
while IFS= read -r -d '' file; do
run gzip -- "$file"
doneIt runs on bash 3.2 and later, on Linux and macOS. More building blocks (temporary directories with cleanup, locks, retries, logging, reading files line by line, the script's own directory) are in references/patterns.md.
Strict mode traps
set -euo pipefail catches a lot, but not everything:
set -eis off inside conditions. A function called asif f; then,f || x,f && x, or! fkeeps running after a failing command inside it. Check exit codes explicitly in functions used as conditions.localandexporthide the exit status.local out=$(cmd)succeeds even whencmdfails. Writelocal out; out=$(cmd).cond && actionas the last statement of a function makes the function return 1 whencondis false, and the caller exits. Useif cond; then action; fi.- Arithmetic that evaluates to 0 fails.
((count++))whencountis 0 returns 1, and bash 4+ exits. Writecount=$((count + 1)). SIGPIPEunderpipefail.producer | head -n 1can fail with status 141 whenheadexits before the producer finishes. Capture the output first (out=$(producer)), or handle 141 explicitly.- Command substitution inside arguments.
echo "$(false)"doesn't stop the script. Assign to a variable first, then use it. - Empty arrays with
set -uon bash before 4.4 (including stock macOS):"${args[@]}"fails with "unbound variable". Write${args[@]+"${args[@]}"}.
Common mistakes
- Looping over files: not
for f in $(ls *.txt), which breaks on spaces and newlines, butfor f in ./*.txt; do [ -e "$f" ] || continue; ...; done. - Reading lines: not
cat file | while read line, butwhile IFS= read -r line || [ -n "$line" ]; do ...; done < file. This keeps backslashes and leading spaces, reads a last line that has no newline, and runs in the current shell. - Passing found files to a command: not
find . -name '*.tmp' | xargs rm, butfind . -name '*.tmp' -exec rm -- {} +(or-print0withxargs -0). - Changing directory:
cd -- "$dir" || exit 1before anything else. Withcd "$dir"; rm -rf ./*, a failedcddeletes the current directory's contents. - Printing data:
printf '%s\n' "$data", notecho "$data";echotreats-n,-e, and backslashes differently in every shell. - Rewriting a file:
sort file > "$tmp" && mv -- "$tmp" file, notsort file > file, which empties the file beforesortreads it. - Home directory:
"$HOME/dir", not"~/dir"; a tilde inside quotes isn't expanded. - Writing a root-owned file:
printf 'x\n' | sudo tee /etc/file >/dev/null(with approval); insudo echo x > /etc/filethe redirect runs as you. - Building commands: an array,
cmd=(tool --flag "$arg"); "${cmd[@]}", nevereval "$cmd $args". - Reading input:
read -r var; without-r, backslashes are lost. - Remote commands:
ssh host "rm -- $(printf '%q' "$path")"; the remote shell parses the string a second time.
One-liners in agent tool calls
- Each tool call usually starts a fresh shell. Variables,
cd,export, and functions don't carry over unless your environment says they do. Use the tool's working-directory option, or chain dependent steps with&&in one call. - The default shell may be zsh (macOS), not bash. In zsh: an unquoted
?,*, or[that matches no file is an error ("no matches found"), so quote URLs and patterns; a word starting with=expands to a command path (echo ===fails); unquoted variables don't split into words;pathis tied toPATHandstatusis read-only, so don't use them as variable names. When a snippet needs bash semantics, run it withbash -c '...'. - Quote paths with spaces or special characters, every time.
- Bound anything that might hang with
timeout(checkcommand -v timeout; elsewhereperl -e 'alarm shift; exec @ARGV' <seconds> <cmd>), and disable pagers (git --no-pager,GH_PAGER=cat,PAGER=cat,--no-pagerflags). - Prefer several clear commands to one clever pipeline when a mistake would be costly. For anything longer than a few lines, write a script file in a temporary directory, lint it, and run it.
Shell inside other files
- Makefiles: each recipe line runs in its own shell, so
cddoesn't persist between lines; join with&&or use.ONESHELL:. Write shell variables as$$var. For bash with strict mode:SHELL := bashand.SHELLFLAGS := -euo pipefail -c..ONESHELLand.SHELLFLAGSneed GNU Make 3.82 or later; macOS's/usr/bin/makeis 3.81 and ignores both without a warning, so checkmake --version. - Dockerfiles:
RUNuses/bin/sh -c, so a failing command before a pipe goes unnoticed. UseSHELL ["/bin/bash", "-o", "pipefail", "-c"]beforeRUNlines with pipes, chain with&&, and clean package caches in the sameRUN. - GitHub Actions: a
run:step withoutshell:usesbash -e {0}on Linux and macOS, withoutpipefail;shell: bashadds-o pipefail. Never put${{ github.event... }}or other user-controlled expressions directly in arun:script, because they're pasted in before the shell parses the script (script injection); pass them throughenv:and use"$VAR". - Git hooks: they inherit the environment of whatever ran git, plus
GIT_*variables. When git runs from a GUI or an IDE, that environment may lack your shell'sPATHadditions. Most hooks run in the repository root, but those that run during a push on the receiving side run in$GIT_DIR. Keep hooks fast, give them an explicit shebang (POSIX sh or bash), and make them executable (chmod +x).
Stop and ask when
- A script would delete, overwrite, or move files outside a directory it created, or touch user data.
- It needs
sudo, changes system configuration, installs software, or edits shell startup files (~/.zshrc,~/.bashrc,~/.profile). - It downloads and runs code from the internet.
- Making it work on both Linux and macOS would add a dependency (GNU coreutils, a newer bash).
- A destructive command needs
--forceor--yes.
Final report
Script: scripts/rotate-logs.sh (new, executable)
Shell: bash 3.2+; tested on macOS (bash 3.2.57) and in debian:stable-slim (bash 5.2)
ShellCheck: clean (0.11.0)
Safety: --dry-run supported; touches only *.log files under the given directory
Ran: --dry-run on ./tmp/logs (would compress 3 files); not run for realTempting shortcuts
| Shortcut | Why not |
| "The variable is always set." | Until a typo, an empty config, or a new caller. ${var:?} costs nothing. |
| "Quotes aren't needed, there are no spaces." | File names, user input, and paths on other machines will have spaces eventually. |
"sed -i works on my machine." | It fails on macOS (or on Linux, with the macOS form). Use the portable form. |
"set -e will catch it." | Not inside conditions, local, or command substitutions. Check what matters explicitly. |
"The install docs pipe curl straight into sh." | You'd run unreviewed code with your permissions. Download, read, verify, then run with approval. |
"I'll add --force so it doesn't prompt." | The prompt was there to stop a destructive mistake. Ask the user. |
| "ShellCheck is noisy." | Most of its warnings are real bugs: quoting, unset variables, ignored exit codes. |
References
| Situation | Read |
| GNU versus BSD (macOS) tool differences, bash 3.2 limits, POSIX sh and dash, detecting the platform | references/portability.md |
| Building blocks: temporary files and cleanup, signal handling, guarded deletes, locks, retries, logging, safe iteration, arrays, in-place edits | references/patterns.md |
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.

