Shell Style Guide
Review and write shell scripts following the Google Shell Style Guide.
When Writing Shell Scripts
Use Bash Only
- Use
#!/bin/bash with minimal flags
- Set shell options via
set rather than shebang flags (e.g., set -o errexit, set -o nounset)
- Caution:
(( )) evaluating to zero returns non-zero, which causes exit under set -e
- If a script exceeds ~100 lines or has complex control flow, recommend rewriting in Python or Go
File Conventions
- Executables: no extension (strongly preferred) or
.sh
- Libraries (sourced only):
.sh extension, not executable
- SUID/SGID: forbidden — use
sudo instead
Error Output
Direct all error messages to STDERR:
err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
}
Review Checklist
When reviewing shell code, check these categories in order:
- Critical bugs — unquoted variables, missing error handling, unsafe
eval
- Formatting — 2-space indent, 80-char line limit, pipeline style
- Naming — lowercase_with_underscores for functions/variables, UPPER_CASE for constants
- Best practices —
[[ ]] over [ ], $(cmd) over backticks, arrays over space-delimited strings
- Structure —
main function pattern, functions grouped at top, comments on non-obvious logic
For detailed rules and examples in each category, see references/style-rules.md.
Key Rules Summary
Formatting
- Indent: 2 spaces, no tabs
- Line length: max 80 characters
- Pipelines: one-liners stay on one line; multi-segment pipelines split with
| on newline
- Loops/conditionals:
; then / ; do on same line as if / for / while
Quoting & Variables
- Always quote strings with variables, command substitutions, spaces, or metacharacters
- Prefer
"${var}" over "$var" (exception: single-char specials like $?, $!, $@ don't need braces)
- Use
"$@" over $*
Features
[[ ... ]] over [ ... ] — avoids pathname expansion and word splitting
$(command) over backticks — cleaner nesting
(( ... )) for arithmetic — never let, $[ ], or expr
- Use arrays for lists — avoid space-delimited strings
- Avoid
eval — use safer alternatives
- Use process substitution
< <(cmd) or readarray instead of piping to while
- Use ShellCheck to catch common bugs
Naming
| Element |
Convention |
Example |
| Functions |
lower_snake_case |
get_user_name() |
| Variables |
lower_snake_case |
local user_name |
| Constants |
UPPER_SNAKE_CASE |
readonly MAX_RETRIES=3 |
| Package separator |
:: |
mypackage::my_func() |
Structure
#!/bin/bash
# File header comment describing the script's purpose.
# Set shell options instead of using shebang flags.
# Note: set -e causes (( )) returning 0 to exit. Use with caution.
# Constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Functions (grouped together, before main)
parse_args() { ... }
validate_input() { ... }
main() {
parse_args "$@"
validate_input
# ...
}
main "$@"
Local Variables
- Declare with
local in functions
- Separate declaration from assignment when capturing command output:
# Correct — exit code is checkable
local my_var
my_var="$(my_func)"
# Wrong — local always returns 0, masking errors
local my_var="$(my_func)"
Return Values & Pipelines
- Always check return values
- For pipelines, check
PIPESTATUS immediately (it resets on next command):
tar -cf - ./* | gzip > archive.tar.gz
if (( PIPESTATUS[0] != 0 || PIPESTATUS[1] != 0 )); then
err "Pipeline failed"
fi
Builtins Over External Commands
Prefer shell builtins for efficiency:
# Prefer
addition=$(( x + y ))
[[ "${input}" =~ ^[0-9]+$ ]]
"${str##*/}" # basename
# Avoid
addition=$(expr "${x}" + "${y}")
echo "${input}" | grep -q '^[0-9]+$'
"$(basename "${str}")"
1---2name: shell-style-guide3description: Review shell/bash code for adherence to Google Shell Style Guide. Use when the user requests a code review of shell scripts (.sh, .bash), writing new shell scripts, fixing shell script issues, or checking shell code against style guidelines. Trigger phrases include: review this shell script, check shell style, write a bash script, review my shell code, or any task involving shell/bash scripting where code quality and consistency matter.4---56# Shell Style Guide78Review and write shell scripts following the [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html).910## When Writing Shell Scripts1112### Use Bash Only1314- Use `#!/bin/bash` with minimal flags15- Set shell options via `set` rather than shebang flags (e.g., `set -o errexit`, `set -o nounset`)16- Caution: `(( ))` evaluating to zero returns non-zero, which causes exit under `set -e`17- If a script exceeds ~100 lines or has complex control flow, recommend rewriting in Python or Go1819### File Conventions2021- Executables: no extension (strongly preferred) or `.sh`22- Libraries (sourced only): `.sh` extension, not executable23- SUID/SGID: forbidden — use `sudo` instead2425### Error Output2627Direct all error messages to STDERR:2829```bash30err() {31 echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&232}33```3435## Review Checklist3637When reviewing shell code, check these categories in order:38391. **Critical bugs** — unquoted variables, missing error handling, unsafe `eval`402. **Formatting** — 2-space indent, 80-char line limit, pipeline style413. **Naming** — lowercase_with_underscores for functions/variables, UPPER_CASE for constants424. **Best practices** — `[[ ]]` over `[ ]`, `$(cmd)` over backticks, arrays over space-delimited strings435. **Structure** — `main` function pattern, functions grouped at top, comments on non-obvious logic4445For detailed rules and examples in each category, see [references/style-rules.md](references/style-rules.md).4647## Key Rules Summary4849### Formatting5051- **Indent**: 2 spaces, no tabs52- **Line length**: max 80 characters53- **Pipelines**: one-liners stay on one line; multi-segment pipelines split with `|` on newline54- **Loops/conditionals**: `; then` / `; do` on same line as `if` / `for` / `while`5556### Quoting & Variables5758- Always quote strings with variables, command substitutions, spaces, or metacharacters59- Prefer `"${var}"` over `"$var"` (exception: single-char specials like `$?`, `$!`, `$@` don't need braces)60- Use `"$@"` over `$*`6162### Features6364- `[[ ... ]]` over `[ ... ]` — avoids pathname expansion and word splitting65- `$(command)` over backticks — cleaner nesting66- `(( ... ))` for arithmetic — never `let`, `$[ ]`, or `expr`67- Use arrays for lists — avoid space-delimited strings68- Avoid `eval` — use safer alternatives69- Use process substitution `< <(cmd)` or `readarray` instead of piping to `while`70- Use ShellCheck to catch common bugs7172### Naming7374| Element | Convention | Example |75|---------|-----------|---------|76| Functions | `lower_snake_case` | `get_user_name()` |77| Variables | `lower_snake_case` | `local user_name` |78| Constants | `UPPER_SNAKE_CASE` | `readonly MAX_RETRIES=3` |79| Package separator | `::` | `mypackage::my_func()` |8081### Structure8283```bash84#!/bin/bash85# File header comment describing the script's purpose.8687# Set shell options instead of using shebang flags.88# Note: set -e causes (( )) returning 0 to exit. Use with caution.8990# Constants91readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"9293# Functions (grouped together, before main)94parse_args() { ... }95validate_input() { ... }9697main() {98 parse_args "$@"99 validate_input100 # ...101}102103main "$@"104```105106### Local Variables107108- Declare with `local` in functions109- Separate declaration from assignment when capturing command output:110111```bash112# Correct — exit code is checkable113local my_var114my_var="$(my_func)"115116# Wrong — local always returns 0, masking errors117local my_var="$(my_func)"118```119120### Return Values & Pipelines121122- Always check return values123- For pipelines, check `PIPESTATUS` immediately (it resets on next command):124125```bash126tar -cf - ./* | gzip > archive.tar.gz127if (( PIPESTATUS[0] != 0 || PIPESTATUS[1] != 0 )); then128 err "Pipeline failed"129fi130```131132### Builtins Over External Commands133134Prefer shell builtins for efficiency:135136```bash137# Prefer138addition=$(( x + y ))139[[ "${input}" =~ ^[0-9]+$ ]]140"${str##*/}" # basename141142# Avoid143addition=$(expr "${x}" + "${y}")144echo "${input}" | grep -q '^[0-9]+$'145"$(basename "${str}")"146```