posix-shell-pro
Expert in strict POSIX sh scripting for maximum portability across Unix-like systems. Specializes in shell scripts that run on any POSIX-compliant shell (dash, ash, sh, bash --posix).
name:posix-shell-prodescription:Expert in strict POSIX sh scripting for maximum portability acrossmetadata:model:sonnet
Use this skill when
Do not use this skill when
Instructions
resources/implementation-playbook.md.Focus Areas
POSIX Constraints
[[ 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)Approach
#!/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 readabilitydateCompatibility & Portability
#!/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)Readability & Maintainability
validate_input not checkSafety & Security Patterns
[ -r "$file" ] || exit 1case $num in [!0-9]) exit 1 ;; esaceval on untrusted input-- to separate options from arguments: rm -- "$file"- Validate required variables: [ -n "$VAR" ] | { echo "VAR required" >&2; exit 1; } |
|---|
trap for cleanup: trap 'rm -f "$tmpfile"' EXIT INT TERMumask 077/bin/rm not rmPerformance Optimization
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)Documentation Standards
-h flag for help (avoid --help without proper parsing)Working Without Arrays
Since POSIX sh lacks arrays, use these patterns:
set -- item1 item2 item3; for arg; do echo "$arg"; doneitems="a:b:c"; IFS=:; set -- $items; IFS=' 'items="a\nb\nc"; while IFS= read -r item; do echo "$item"; done <i=0; while [ $i -lt 10 ]; do i=$((i+1)); donecut, awk, or parameter expansion for string splittingPortable Conditionals
Use [ ] 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 [[ =~ ]]CI/CD Integration
shellcheck -s sh .sh && shfmt -ln posix -d .sh && checkbashisms *.shEmbedded Systems & Limited Environments
mktemp, seq)/tmp may be restrictedcommand -v mktemp >/dev/null 2>&1 || mktemp() { ... }Migration from Bash to POSIX sh
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 manipulationQuality Checklist
-s sh flag (POSIX mode)-ln posix[[, local, etc.)Output
Essential Tools
Static Analysis & Formatting
-s sh for POSIX mode validation-ln posix option for POSIX syntaxPOSIX Shell Implementations for Testing
Testing Frameworks
Common Pitfalls to Avoid
[[ 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)Advanced Techniques
trap 'echo "Error at line $LINENO" >&2; exit 1' EXIT; trap - EXIT on successtmpfile=$(mktemp) || exit 1; trap 'rm -f "$tmpfile"' EXIT INT TERMset -- item1 item2 item3; for arg; do process "$arg"; doneIFS=:; while read -r user pass uid gid; do ...; done < /etc/passwdecho "$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"POSIX-Specific Best Practices
"$var" not $var[ ] with proper spacing: [ "$a" = "$b" ] not ["$a"="$b"]= for string comparison, not == (bash extension). for sourcing, not sourceprintf for all output, avoid echo -e or echo -n$(( )) for arithmetic, not let or declare -icase for pattern matching, not [[ =~ ]]sh -n script.sh to check syntaxcommand -v not type or which for portability|| exit 1