resources/implementation-playbook.md.[[ conditionals (use [ test command only)<() or >()
{1..10}
local keyword (use function-scoped variables carefully)declare, typeset, or readonly for variable attributes+= operator for string concatenation${var//pattern/replacement} substitutionsource command (use . for sourcing files)#!/bin/sh shebang for POSIX shellset -eu for error handling (no pipefail in POSIX)"$var" never $var
[ ] for all conditional tests, never [[
while and case (no getopts for long options)mktemp and cleanup trapsprintf instead of echo for all output (echo behavior varies). script.sh instead of source script.sh for sourcing|| exit 1 checksIFS manipulation carefully and restore original value[ -n "$var" ] and [ -z "$var" ] tests-- and use rm -rf -- "$dir" for safety$() instead of backticks for readabilitydate
#!/bin/sh to invoke the system's POSIX shelluname -s for OS detectioncommand -v instead of which (more portable)command -v cmd >/dev/null 2>&1 || exit 1
[ -e "$file" ] for existence checks (works on all systems)/dev/stdin, /dev/stdout (not universally available)&> (bash-specific)validate_input not check
[ -r "$file" ] || exit 1
case $num in *[!0-9]*) exit 1 ;; esac
eval on untrusted input-- to separate options from arguments: rm -- "$file"
[ -n "$VAR" ] || { echo "VAR required" >&2; exit 1; }
cmd || { echo "failed" >&2; exit 1; }
trap for cleanup: trap 'rm -f "$tmpfile"' EXIT INT TERM
umask 077
/bin/rm not rm
while read not for i in $(cat)
case for multiple string comparisons (faster than repeated if)expr or $(( )) for arithmetic (POSIX supports $(( )))grep -q when you only need true/false (faster than capturing output)-h flag for help (avoid --help without proper parsing)Since POSIX sh lacks arrays, use these patterns:
set -- item1 item2 item3; for arg; do echo "$arg"; done
items="a:b:c"; IFS=:; set -- $items; IFS=' '
items="a\nb\nc"; while IFS= read -r item; do echo "$item"; done <<EOF
i=0; while [ $i -lt 10 ]; do i=$((i+1)); done
cut, awk, or parameter expansion for string splittingUse [ ] test command with POSIX operators:
[ -e file ] exists, [ -f file ] regular file, [ -d dir ] directory[ -z "$str" ] empty, [ -n "$str" ] not empty, [ "$a" = "$b" ] equal[ "$a" -eq "$b" ] equal, [ "$a" -lt "$b" ] less than[ cond1 ] && [ cond2 ] AND, [ cond1 ] || [ cond2 ] OR[ ! -f file ] not a filecase not [[ =~ ]]
shellcheck -s sh *.sh && shfmt -ln posix -d *.sh && checkbashisms *.sh
mktemp, seq)/tmp may be restrictedcommand -v mktemp >/dev/null 2>&1 || mktemp() { ... }
checkbashisms to identify bash-specific constructs[[ with [ and adjust regex to case patternslocal keyword, use function prefixes instead<() with temporary files or pipessed/awk for complex string manipulation-s sh flag (POSIX mode)-ln posix
[[, local, etc.)-s sh for POSIX mode validation-ln posix option for POSIX syntax[[ instead of [ (bash-specific)local keyword (bash/ksh extension)echo without printf (behavior varies across implementations)source instead of . for sourcing scripts${var//pattern/replacement}
<() or >()
function keyword (ksh/bash syntax)$RANDOM variable (not in POSIX)read -a for arrays (bash-specific)set -o pipefail (bash-specific)&> for redirection (use >file 2>&1)trap 'echo "Error at line $LINENO" >&2; exit 1' EXIT; trap - EXIT on successtmpfile=$(mktemp) || exit 1; trap 'rm -f "$tmpfile"' EXIT INT TERM
set -- item1 item2 item3; for arg; do process "$arg"; done
IFS=:; while read -r user pass uid gid; do ...; done < /etc/passwd
echo "$str" | sed 's/old/new/g' or use parameter expansion ${str%suffix}
value=${var:-default} assigns default if var unset or nullfunction keyword, use func_name() { ... }
(cd dir && cmd) changes directory without affecting parentcat <<'EOF' with quotes prevents variable expansioncommand -v cmd >/dev/null 2>&1 && echo "found" || echo "missing"
"$var" not $var
[ ] with proper spacing: [ "$a" = "$b" ] not ["$a"="$b"]
= for string comparison, not == (bash extension). for sourcing, not source
printf for all output, avoid echo -e or echo -n
$(( )) for arithmetic, not let or declare -i
case for pattern matching, not [[ =~ ]]
sh -n script.sh to check syntaxcommand -v not type or which for portability|| exit 1