Use this skill when
Working on posix shell pro tasks or workflowsNeeding guidance, best practices, or checklists for posix shell proDo not use this skill when
The task is unrelated to posix shell proYou need a different domain or tool outside this scopeInstructions
Clarify goals, constraints, and required inputs.Apply relevant best practices and validate outcomes.Provide actionable steps and verification.If detailed examples are required, open resources/implementation-playbook.md.Focus Areas
Strict POSIX compliance for maximum portabilityShell-agnostic scripting that works on any Unix-like systemDefensive programming with portable error handlingSafe argument parsing without bash-specific featuresPortable file operations and resource managementCross-platform compatibility (Linux, BSD, Solaris, AIX, macOS)Testing with dash, ash, and POSIX mode validationStatic analysis with ShellCheck in POSIX modeMinimalist approach using only POSIX-specified featuresCompatibility with legacy systems and embedded environmentsPOSIX Constraints
No arrays (use positional parameters or delimited strings)No [[ conditionals (use [ test command only)No process substitution <() or >()No brace expansion {1..10}No local keyword (use function-scoped variables carefully)No declare, typeset, or readonly for variable attributesNo += operator for string concatenationNo ${var//pattern/replacement} substitutionNo associative arrays or hash tablesNo source command (use . for sourcing files)Approach
Always use #!/bin/sh shebang for POSIX shellUse set -eu for error handling (no pipefail in POSIX)Quote all variable expansions: "$var" never $varUse [ ] for all conditional tests, never [[Implement argument parsing with while and case (no getopts for long options)Create temporary files safely with mktemp and cleanup trapsUse printf instead of echo for all output (echo behavior varies)Use . script.sh instead of source script.sh for sourcingImplement error handling with explicit || exit 1 checksDesign scripts to be idempotent and support dry-run modesUse IFS manipulation carefully and restore original valueValidate inputs with [ -n "$var" ] and [ -z "$var" ] testsEnd option parsing with -- and use rm -rf -- "$dir" for safetyUse command substitution $() instead of backticks for readabilityImplement structured logging with timestamps using dateTest scripts with dash/ash to verify POSIX complianceCompatibility & Portability
Use #!/bin/sh to invoke the system's POSIX shellTest on multiple shells: dash (Debian/Ubuntu default), ash (Alpine/BusyBox), bash --posixAvoid GNU-specific options; use POSIX-specified flags onlyHandle platform differences: uname -s for OS detectionUse command -v instead of which (more portable)Check for command availability: command -v cmd >/dev/null 2>&1 || exit 1Provide portable implementations for missing utilitiesUse [ -e "$file" ] for existence checks (works on all systems)Avoid /dev/stdin, /dev/stdout (not universally available)Use explicit redirection instead of &> (bash-specific)Readability & Maintainability
Use descriptive variable names in UPPER_CASE for exports, lower_case for localsAdd section headers with comment blocks for organizationKeep functions under 50 lines; extract complex logicUse consistent indentation (spaces only, typically 2 or 4)Document function purpose and parameters in commentsUse meaningful names: validate_input not checkAdd comments for non-obvious POSIX workaroundsGroup related functions with descriptive headersExtract repeated code into functionsUse blank lines to separate logical sectionsSafety & Security Patterns
Quote all variable expansions to prevent word splittingValidate file permissions before operations: [ -r "$file" ] || exit 1Sanitize user input before using in commandsValidate numeric input: case $num in [!0-9]) exit 1 ;; esacNever use eval on untrusted inputUse -- to separate options from arguments: rm -- "$file"- Validate required variables: [ -n "$VAR" ] | { echo "VAR required" >&2; exit 1; } |
|---|
Use trap for cleanup: trap 'rm -f "$tmpfile"' EXIT INT TERMSet restrictive umask for sensitive files: umask 077Log security-relevant operations to syslog or fileValidate file paths don't contain unexpected charactersUse full paths for commands in security-critical scripts: /bin/rm not rmPerformance Optimization
Use shell built-ins over external commands when possibleAvoid spawning subshells in loops: use while read not for i in $(cat)Cache command results in variables instead of repeated executionUse case for multiple string comparisons (faster than repeated if)Process files line-by-line for large filesUse expr or $(( )) for arithmetic (POSIX supports $(( )))Minimize external command calls in tight loopsUse grep -q when you only need true/false (faster than capturing output)Batch similar operations togetherUse here-documents for multi-line strings instead of multiple echo callsDocumentation Standards
Implement -h flag for help (avoid --help without proper parsing)Include usage message showing synopsis and optionsDocument required vs optional arguments clearlyList exit codes: 0=success, 1=error, specific codes for specific failuresDocument prerequisites and required commandsAdd header comment with script purpose and authorInclude examples of common usage patternsDocument environment variables used by scriptProvide troubleshooting guidance for common issuesNote POSIX compliance in documentationWorking Without Arrays
Since POSIX sh lacks arrays, use these patterns:
Positional Parameters: set -- item1 item2 item3; for arg; do echo "$arg"; doneDelimited Strings: items="a:b:c"; IFS=:; set -- $items; IFS=' 'Newline-Separated: items="a\nb\nc"; while IFS= read -r item; do echo "$item"; done <Counters: i=0; while [ $i -lt 10 ]; do i=$((i+1)); doneField Splitting: Use cut, awk, or parameter expansion for string splittingPortable Conditionals
Use [ ] test command with POSIX operators:
File Tests: [ -e file ] exists, [ -f file ] regular file, [ -d dir ] directoryString Tests: [ -z "$str" ] empty, [ -n "$str" ] not empty, [ "$a" = "$b" ] equalNumeric Tests: [ "$a" -eq "$b" ] equal, [ "$a" -lt "$b" ] less thanLogical: [ cond1 ] && [ cond2 ] AND, [ cond1 ] || [ cond2 ] ORNegation: [ ! -f file ] not a filePattern Matching: Use case not [[ =~ ]]CI/CD Integration
Matrix testing: Test across dash, ash, bash --posix, yash on Linux, macOS, AlpineContainer testing: Use alpine:latest (ash), debian:stable (dash) for reproducible testsPre-commit hooks: Configure checkbashisms, shellcheck -s sh, shfmt -ln posixGitHub Actions: Use shellcheck-problem-matchers with POSIX modeCross-platform validation: Test on Linux, macOS, FreeBSD, NetBSDBusyBox testing: Validate on BusyBox environments for embedded systemsAutomated releases: Tag versions and generate portable distribution packagesCoverage tracking: Ensure test coverage across all POSIX shellsExample workflow: shellcheck -s sh .sh && shfmt -ln posix -d .sh && checkbashisms *.shEmbedded Systems & Limited Environments
BusyBox compatibility: Test with BusyBox's limited ash implementationAlpine Linux: Default shell is BusyBox ash, not bashResource constraints: Minimize memory usage, avoid spawning excessive processesMissing utilities: Provide fallbacks when common tools unavailable (mktemp, seq)Read-only filesystems: Handle scenarios where /tmp may be restrictedNo coreutils: Some environments lack GNU coreutils extensionsSignal handling: Limited signal support in minimal environmentsStartup scripts: Init scripts must be POSIX for maximum compatibilityExample: Check for mktemp: command -v mktemp >/dev/null 2>&1 || mktemp() { ... }Migration from Bash to POSIX sh
Assessment: Run checkbashisms to identify bash-specific constructsArray elimination: Convert arrays to delimited strings or positional parametersConditional updates: Replace [[ with [ and adjust regex to case patternsLocal variables: Remove local keyword, use function prefixes insteadProcess substitution: Replace <() with temporary files or pipesParameter expansion: Use sed/awk for complex string manipulationTesting strategy: Incremental conversion with continuous validationDocumentation: Note any POSIX limitations or workaroundsGradual migration: Convert one function at a time, test thoroughlyFallback support: Maintain dual implementations during transition if neededQuality Checklist
Scripts pass ShellCheck with -s sh flag (POSIX mode)Code is formatted consistently with shfmt using -ln posixTest on multiple shells: dash, ash, bash --posix, yashAll variable expansions are properly quotedNo bash-specific features used (arrays, [[, local, etc.)Error handling covers all failure modesTemporary resources cleaned up with EXIT trapScripts provide clear usage informationInput validation prevents injection attacksScripts portable across Unix-like systems (Linux, BSD, Solaris, macOS, Alpine)BusyBox compatibility validated for embedded use casesNo GNU-specific extensions or flags usedOutput
POSIX-compliant shell scripts maximizing portabilityTest suites using shellspec or bats-core validating across dash, ash, yashCI/CD configurations for multi-shell matrix testingPortable implementations of common patterns with fallbacksDocumentation on POSIX limitations and workarounds with examplesMigration guides for converting bash scripts to POSIX sh incrementallyCross-platform compatibility matrices (Linux, BSD, macOS, Solaris, Alpine)Performance benchmarks comparing different POSIX shellsFallback implementations for missing utilities (mktemp, seq, timeout)BusyBox-compatible scripts for embedded and container environmentsPackage distributions for various platforms without bash dependencyEssential Tools
Static Analysis & Formatting
ShellCheck: Static analyzer with -s sh for POSIX mode validationshfmt: Shell formatter with -ln posix option for POSIX syntaxcheckbashisms: Detects bash-specific constructs in scripts (from devscripts)Semgrep: SAST with POSIX-specific security rulesCodeQL: Security scanning for shell scriptsPOSIX Shell Implementations for Testing
dash: Debian Almquist Shell - lightweight, strict POSIX compliance (primary test target)ash: Almquist Shell - BusyBox default, embedded systemsyash: Yet Another Shell - strict POSIX conformance validationposh: Policy-compliant Ordinary Shell - Debian policy complianceosh: Oil Shell - modern POSIX-compatible shell with better error messagesbash --posix: GNU Bash in POSIX mode for compatibility testingTesting Frameworks
bats-core: Bash testing framework (works with POSIX sh)shellspec: BDD-style testing that supports POSIX shshunit2: xUnit-style framework with POSIX sh supportsharness: Test framework used by Git (POSIX-compatible)Common Pitfalls to Avoid
Using [[ instead of [ (bash-specific)Using arrays (not in POSIX sh)Using local keyword (bash/ksh extension)Using echo without printf (behavior varies across implementations)Using source instead of . for sourcing scriptsUsing bash-specific parameter expansion: ${var//pattern/replacement}Using process substitution <() or >()Using function keyword (ksh/bash syntax)Using $RANDOM variable (not in POSIX)Using read -a for arrays (bash-specific)Using set -o pipefail (bash-specific)Using &> for redirection (use >file 2>&1)Advanced Techniques
Error Trapping: trap 'echo "Error at line $LINENO" >&2; exit 1' EXIT; trap - EXIT on successSafe Temp Files: tmpfile=$(mktemp) || exit 1; trap 'rm -f "$tmpfile"' EXIT INT TERMSimulating Arrays: set -- item1 item2 item3; for arg; do process "$arg"; doneField Parsing: IFS=:; while read -r user pass uid gid; do ...; done < /etc/passwdString Replacement: echo "$str" | sed 's/old/new/g' or use parameter expansion ${str%suffix}Default Values: value=${var:-default} assigns default if var unset or nullPortable Functions: Avoid function keyword, use func_name() { ... }Subshell Isolation: (cd dir && cmd) changes directory without affecting parentHere-documents: cat <<'EOF' with quotes prevents variable expansionCommand Existence: command -v cmd >/dev/null 2>&1 && echo "found" || echo "missing"POSIX-Specific Best Practices
Always quote variable expansions: "$var" not $varUse [ ] with proper spacing: [ "$a" = "$b" ] not ["$a"="$b"]Use = for string comparison, not == (bash extension)Use . for sourcing, not sourceUse printf for all output, avoid echo -e or echo -nUse $(( )) for arithmetic, not let or declare -iUse case for pattern matching, not [[ =~ ]]Test scripts with sh -n script.sh to check syntaxUse command -v not type or which for portabilityExplicitly handle all error conditions with || exit 1References & Further Reading
POSIX Standards & Specifications
POSIX Shell Command Language - Official POSIX.1-2024 specificationPOSIX Utilities - Complete list of POSIX-mandated utilitiesAutoconf Portable Shell Programming - Comprehensive portability guide from GNUPortability & Best Practices
Rich's sh (POSIX shell) tricks - Advanced POSIX shell techniquesSuckless Shell Style Guide - Minimalist POSIX sh patternsFreeBSD Porter's Handbook - Shell - BSD portability considerationsTools & Testing
checkbashisms - Detect bash-specific constructs