standards-shell
Skill0 starsChanged 4 months agoMITView on GitHub
Use this skill whenever you write, review, or design shell or Bash scripts, including CI/CD pipeline steps, automation scripts, and commands saved to a file, even if the user does not mention style. Provides Google Shell Style Guide patterns, safety practices, and idiomatic Bash. Load standards-code for the language-neutral baseline.
- Deletes or force-pushes
In this repository34 other files it gives its agents.
- AGENTS.mdAGENTS.md
- mcpSkill
- pattern-orchestration-complexSkill
- pattern-task-breakdownSkill
- role-architectSkill
- role-code-reviewSkill
- role-data-engineerSkill
- role-developerSkill
- role-orchestratorSkill
- role-product-engineerSkill
- role-qa-engineerSkill
- role-security-auditorSkill
- role-site-reliability-engineerSkill
- role-technical-writerSkill
- standards-analysisSkill
- standards-api-designSkill
- standards-codeSkill
- standards-databaseSkill
- standards-documentationSkill
- standards-go-concurrencySkill
- standards-go-performanceSkill
- standards-goSkill
- standards-go-testingSkill
- standards-markdownSkill
- standards-observabilitySkill
- standards-pulumi-typescriptSkill
- standards-pythonSkill
- standards-securitySkill
- standards-skill-authoringSkill
- standards-testingSkill
- standards-typescriptSkill
- tool-storeSkill
- workflow-debuggingSkill
- workflow-pi-extensionSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
27 rules. About 0 are common, 10 are distinctive, and 17 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈10
Distinctive · estimate
This author's own choices. Worth a look.
17
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 15
Use #!/bin/bash as the shebang for executables
- Very specific: line 16
Use [[ … ]] over [ … ] or test
- Very specific: line 17
Use $(command) over backticks
- Very specific: line 19
Use local for function-scoped variables
- Distinctive: line 21
Run ShellCheck on all scripts
- Distinctive: line 22
Use arrays for argument lists
- Distinctive: line 26
Write scripts longer than ~100 lines: rewrite in Python/Go
- Very specific: line 27
Use eval: it obscures what gets set and can't be audited
- Very specific: line 28
Use let, $[ … ], or expr for arithmetic: use (( … ))
- Very specific: line 30
Use backticks: use $(…) instead
- Very specific: line 31
Use $* for passing arguments: use "$@"
- Distinctive: line 32
Use SUID/SGID on shell scripts
- Very specific: line 34
Use [ … ] when [[ … ]] is available
- Very specific: line 85
Libraries: must have .sh extension and should not be executable
- Very specific: line 98
All error messages go to STDERR.
- Distinctive: line 118
Every file must have a top-level comment with a brief description:
- Very specific: line 184
If a pipeline fits on one line, keep it on one line.
- Very specific: line 211
Always include in "$@" in for loops explicitly:
- Very specific: line 239
Prefer "${var}" over "$var".
- Distinctive: line 254
Always quote strings containing variables, substitutions, spaces, or shell meta characters
- Very specific: line 255
Use "$@" (not $*) when passing all arguments
- Distinctive: line 256
Use arrays for safe quoting of argument lists
- Distinctive: line 277
Read shell features and pitfalls when selecting shell constructs or reviewing portability hazards.
- Very specific: line 352
Place all functions together, just below constants.
- Very specific: line 375
Always check return values.
- Distinctive: line 399
Prefer shell builtins and parameter expansion over spawning external processes:
- Distinctive: line 417
Before committing a shell script:
≈ “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 Standards
Provides: Idiomatic bash patterns, naming conventions, formatting rules, safety practices, and common pitfalls based on the Google Shell Style Guide.
Quick Reference
Golden Rule: Shell is for small utilities and simple wrappers: when in doubt, use a real language.
Do (✅):
- Use
#!/bin/bashas the shebang for executables - Use
[[ … ]]over[ … ]ortest - Use
$(command)over backticks - Quote all variables:
"${var}" - Use
localfor function-scoped variables - Send errors to STDERR:
echo "error" >&2 - Run ShellCheck on all scripts
- Use arrays for argument lists
- Declare constants with
readonly; exported vars withdeclare -xr
Don't (❌):
- Write scripts longer than ~100 lines: rewrite in Python/Go
- Use
eval: it obscures what gets set and can't be audited - Use
let,$[ … ], orexprfor arithmetic: use(( … )) - Pipe into
while read: use process substitution< <(cmd)orreadarray - Use backticks: use
$(…)instead - Use
$*for passing arguments: use"$@" - Use SUID/SGID on shell scripts
- Define aliases in scripts: use functions
- Use
[ … ]when[[ … ]]is available
Key tools:
shellcheck script.sh # static analysis (required)
shellcheck -S warning *.sh # run at warning severity
bash -n script.sh # syntax check only
bash -x script.sh # trace executionWhen to Use Shell
Shell should only be used for small utilities or simple wrapper scripts.
Switch to Python, Go, or another structured language when:
- The script exceeds ~100 lines
- Control flow is complex (non-trivial branching, recursion)
- Performance matters
- The script is not easily understood by others
File Structure & Invocation
Shebang
#!/bin/bashExecutables must start with #!/bin/bash. Use set to configure shell options so the script still works when called directly as bash script.sh.
Recommended safety options at the top of every non-trivial script:
#!/bin/bash
set -euo pipefail| Option | Effect |
-e | Exit immediately on error |
-u | Treat unset variables as errors |
-o pipefail | Pipe returns non-zero if any command fails |
File Extensions
- Executables:
.shextension or no extension (no extension preferred when added toPATH) - Libraries: must have
.shextension and should not be executable - Source filenames: lowercase with underscores:
make_template.shormaketemplate.sh(notmake-template.sh)
SUID/SGID
SUID and SGID are forbidden on shell scripts. Use sudo to provide elevated access.
Environment
STDOUT vs STDERR
All error messages go to STDERR. Normal output goes to STDOUT.
# Standard error helper
err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
}
if ! do_something; then
err "Unable to do_something"
exit 1
fiComments
File Header
Every file must have a top-level comment with a brief description:
#!/bin/bash
#
# Perform hot backups of Oracle databases.Function Comments
Any non-obvious or non-trivial function must have a header comment. Library functions always require one. Use this format:
#######################################
# Delete a file in a sophisticated manner.
# Globals:
# BACKUP_DIR
# Arguments:
# File to delete, a path.
# Outputs:
# Writes status to STDOUT.
# Returns:
# 0 if deleted, non-zero on error.
#######################################
del_thing() {
rm "$1"
}Implementation Comments
Comment tricky, non-obvious, or important parts of your code. Don't comment everything: only what isn't self-evident.
TODO Comments
# TODO(username): Handle the unlikely edge cases (bug ####)Formatting
Indentation
- 2 spaces: no tabs
- Blank lines between blocks improve readability
- Exception:
<<-here-documents may use tab indentation
Line Length
Maximum 80 characters. For longer strings, use here-documents or embedded newlines:
# DO: here document
cat <<END
I am an exceptionally long string.
END
# DO: embedded newline
long_string="I am an exceptionally
long string."Pipelines
If a pipeline fits on one line, keep it on one line. Otherwise, split with \ and one pipe per line (pipe on the newline):
# Short: one line
command1 | command2
# Long: split per segment
command1 \
| command2 \
| command3 \
| command4Control Flow
; then and ; do go on the same line as if/for/while. else and closing fi/done on their own lines:
for dir in "${dirs[@]}"; do
if [[ -d "${dir}" ]]; then
rm -rf "${dir}"
else
mkdir -p "${dir}"
fi
doneAlways include in "$@" in for loops explicitly:
for arg in "$@"; do
echo "argument: ${arg}"
doneCase Statement
case "${expression}" in
a)
variable="…"
;;
b) simple_action ;;
*)
error "Unexpected: '${expression}'"
;;
esac- Indent alternatives by 2 spaces
- One-line alternatives: space after
), space before;; - Multi-command alternatives: pattern, actions, and
;;on separate lines
Variable Expansion
Prefer "${var}" over "$var". Always quote variables.
# Preferred
echo "PATH=${PATH}, mine=${some_var}"
# Braces required for 10th+ positional parameter
echo "${10}"
# Disambiguate concatenation
echo "${1}0${2}0${3}0"Quoting
- Always quote strings containing variables, substitutions, spaces, or shell meta characters
- Use
"$@"(not$*) when passing all arguments - Use arrays for safe quoting of argument lists
- Single quotes indicate no substitution:
'literal $string' - Double quotes allow substitution:
"hello ${name}"
# Quote command substitutions
flag="$(some_command "$@")"
# Use arrays for argument lists
declare -a FLAGS
FLAGS=(--foo --bar='baz')
mybinary "${FLAGS[@]}"
# "$@" preserves argument boundaries; $* does not
run_cmd "$@"Features & Pitfalls
Read shell features and pitfalls when selecting shell constructs or reviewing portability hazards.
Naming Conventions
Function Names
Lowercase, words separated by underscores. No spaces around parentheses. Opening brace on the same line:
# Single function
my_func() {
…
}
# Library function (package::function)
mypackage::my_func() {
…
}The function keyword is optional but must be used consistently within a project.
Variable Names
Lowercase with underscores (same rules as function names):
for zone in "${zones[@]}"; do
something_with "${zone}"
doneConstants and Environment Variables
UPPER_SNAKE_CASE. Declare with readonly and/or export at the top of the file:
# Constant
readonly PATH_TO_FILES='/some/path'
# Exported constant
declare -xr ORACLE_SID='PROD'
# Runtime constant: set readonly after assignment
ZIP_VERSION="$(dpkg --status zip | sed -n 's/^Version: //p')"
readonly ZIP_VERSIONUse Local Variables
Declare function-scoped variables with local. Keep declaration and assignment separate when the value comes from a command substitution: local does not propagate exit codes:
my_func() {
local name="$1"
# Separate declaration and command-substitution assignment
local my_var
my_var="$(my_other_func)"
(( $? == 0 )) || return
echo "${my_var}"
}# ❌ $? reflects 'local', not my_other_func
my_func() {
local my_var="$(my_other_func)"
(( $? == 0 )) || return
}Function Location
Place all functions together, just below constants. Don't scatter executable code between function definitions. Only source/. statements, set options, and constant declarations may precede functions.
main
For scripts with more than one function, define a main function and call it as the last non-comment line:
main() {
local input="$1"
process "${input}"
}
main "$@"This keeps top-level variables local and makes the entry point obvious.
Calling Commands
Checking Return Values
Always check return values. Use if ! or $? checks with informative error messages:
if ! mv "${file_list[@]}" "${dest_dir}/"; then
echo "Unable to move files to ${dest_dir}" >&2
exit 1
fiFor pipelines, capture PIPESTATUS immediately (it's overwritten by the next command):
tar -cf - ./* | ( cd "${dir}" && tar -xf - )
return_codes=( "${PIPESTATUS[@]}" )
if (( return_codes[0] != 0 )); then
err "tar failed"
fi
if (( return_codes[1] != 0 )); then
err "untar failed"
fiBuiltins vs External Commands
Prefer shell builtins and parameter expansion over spawning external processes:
# ✅ Builtin parameter expansion
addition="$(( X + Y ))"
substitution="${string/#foo/bar}"
# ❌ Spawning external processes unnecessarily
addition="$(expr "${X}" + "${Y}")"
substitution="$(echo "${string}" | sed -e 's/^foo/bar/')"Builtins are faster, more portable, and avoid subshell overhead.
Checklist
Before committing a shell script:
- ShellCheck passes with no errors or warnings
- File has a header comment describing its purpose
#!/bin/bashshebang present on executables- Safety options set (
set -euo pipefail) where appropriate - All variables quoted:
"${var}" - Functions use
localfor internal variables - Constants declared
readonly [[ … ]]used instead of[ … ]$(…)used instead of backticks- Arrays used for argument/element lists (not strings)
- No
eval,let,$[ … ], orexpr - Error messages sent to STDERR (
>&2) - Return values checked on all commands
"$@"used (not$*) when passing arguments through- Pipes to
whilereplaced with process substitution orreadarray - Script is ≤ ~100 lines; longer scripts rewritten in a structured language
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.

