POSIX Shell Scripting Cheat Sheet (Version 1.0)
Compact version of the POSIX Shell Scripting Style.
1. Scope and Portability
- Write for POSIX
/bin/sh, not Bash, Z shell, or other extensions. - Executable scripts start with
#!/bin/sh. - Sourced shell fragments start with
# shellcheck shell=sh. - Use
set -euin executable scripts. - Use
if,||, or&&to handle expected command failures explicitly. - Run ShellCheck using the
shdialect.
2. General Layout
- Comments and identifiers are in English.
- Tabs indent code (tab = 4 spaces); spaces only align text output and here docs.
- Maximum 80 characters per source line, including the line break.
- Two blank lines between top-level functions; at most one inside a function.
- Break long commands before a control operator, pipeline, redirection, or argument.
- Indent continuation lines once and end every continued line with
\.
3. Naming and Variables
- Functions and variables:
camelCase. - Environment variables, exported values, and constants:
UPPER_CASE. - Function-private variables use a short function-specific underscore prefix.
- Do not use
local; POSIX shell does not specify it. - Assign without spaces around
=. - Quote variable assignments, parameter expansions, and command substitutions as needed.
4. Quoting and Expansions
- Quote parameter expansions and command substitutions by default.
- Forward arguments with
"$@". - Use
${name}when adjoining literal text or clarifying expansion boundaries. - Use
$( ... ), never backticks. - Avoid
evalunless POSIX shell has no direct alternative. evalmay access a variable with a validated dynamic name or run a command built from correctly quoted parts.- Never pass unvalidated or externally controlled text to
eval. - Never parse
lsor use unquoted command substitution as arguments. - Do not split file names on whitespace.
5. Commands, Output, and Exit Status
- Use
printf, neverecho; always supply a format string. - Normal output goes to stdout, diagnostics to stderr.
- Value-producing functions write only their value to stdout.
- Use
[ ... ]for portable tests and quote operands when appropriate. - Use
casefor pattern matching. - Use
command -v, neverwhich, to find a command.
6. Functions and Parameters
- Declare functions as
name( ), with{on the following line. - Never use the
functionkeyword. - Document each parameter directly above the function using
$1,$2, and$@. - Use parameters for input; use stdout and exit status for results.
- Avoid modifying global state unless that is the function's explicit purpose.
- Call functions without a space between the name and arguments.
7. Control Flow
- Put
then,do,else,elif,esac, anddoneon their own lines. - Indent bodies by one tab.
- Quote the
casevalue but not the patterns. - Indent every
casearm and end it with;;unless it exits. - Read input with
while IFS= read -r line. - Restore
IFSafter changing it temporarily.
8. Files, Temporary Data, and Cleanup
- Use
mktemp -dfor temporary directories. - Install a cleanup
trapas soon as temporary data is created. - Quote every file path and validate caller-provided paths before use.
- Never use predictable temporary names.
- Prefer explicit files and loops when data may contain newlines.
9. Comments and File Headers
- Use
#comments to explain intent, portability, and safety decisions. - Put the file header after the shebang or ShellCheck directive.
- Follow the project format for copyright and SPDX information.
- Put parameter comments immediately above their function.
10. Consistency and Readability Win
- Keep related commands and
casearms visually consistent. - Prefer surrounding-project conventions where multiple valid forms exist.
- Readability, portability, and safe file-name handling beat terse idioms.