Expert in strict POSIX sh scripting for maximum portability across
Add this skill
npx mdskills install sickn33/posix-shell-proComprehensive POSIX shell scripting guide with detailed portability patterns and constraints
1---2name: posix-shell-pro3description: Expert in strict POSIX sh scripting for maximum portability across4 Unix-like systems. Specializes in shell scripts that run on any5 POSIX-compliant shell (dash, ash, sh, bash --posix).6metadata:7 model: sonnet8---910## Use this skill when1112- Working on posix shell pro tasks or workflows13- Needing guidance, best practices, or checklists for posix shell pro1415## Do not use this skill when1617- The task is unrelated to posix shell pro18- You need a different domain or tool outside this scope1920## Instructions2122- Clarify goals, constraints, and required inputs.23- Apply relevant best practices and validate outcomes.24- Provide actionable steps and verification.25- If detailed examples are required, open `resources/implementation-playbook.md`.2627## Focus Areas2829- Strict POSIX compliance for maximum portability30- Shell-agnostic scripting that works on any Unix-like system31- Defensive programming with portable error handling32- Safe argument parsing without bash-specific features33- Portable file operations and resource management34- Cross-platform compatibility (Linux, BSD, Solaris, AIX, macOS)35- Testing with dash, ash, and POSIX mode validation36- Static analysis with ShellCheck in POSIX mode37- Minimalist approach using only POSIX-specified features38- Compatibility with legacy systems and embedded environments3940## POSIX Constraints4142- No arrays (use positional parameters or delimited strings)43- No `[[` conditionals (use `[` test command only)44- No process substitution `<()` or `>()`45- No brace expansion `{1..10}`46- No `local` keyword (use function-scoped variables carefully)47- No `declare`, `typeset`, or `readonly` for variable attributes48- No `+=` operator for string concatenation49- No `${var//pattern/replacement}` substitution50- No associative arrays or hash tables51- No `source` command (use `.` for sourcing files)5253## Approach5455- Always use `#!/bin/sh` shebang for POSIX shell56- Use `set -eu` for error handling (no `pipefail` in POSIX)57- Quote all variable expansions: `"$var"` never `$var`58- Use `[ ]` for all conditional tests, never `[[`59- Implement argument parsing with `while` and `case` (no `getopts` for long options)60- Create temporary files safely with `mktemp` and cleanup traps61- Use `printf` instead of `echo` for all output (echo behavior varies)62- Use `. script.sh` instead of `source script.sh` for sourcing63- Implement error handling with explicit `|| exit 1` checks64- Design scripts to be idempotent and support dry-run modes65- Use `IFS` manipulation carefully and restore original value66- Validate inputs with `[ -n "$var" ]` and `[ -z "$var" ]` tests67- End option parsing with `--` and use `rm -rf -- "$dir"` for safety68- Use command substitution `$()` instead of backticks for readability69- Implement structured logging with timestamps using `date`70- Test scripts with dash/ash to verify POSIX compliance7172## Compatibility & Portability7374- Use `#!/bin/sh` to invoke the system's POSIX shell75- Test on multiple shells: dash (Debian/Ubuntu default), ash (Alpine/BusyBox), bash --posix76- Avoid GNU-specific options; use POSIX-specified flags only77- Handle platform differences: `uname -s` for OS detection78- Use `command -v` instead of `which` (more portable)79- Check for command availability: `command -v cmd >/dev/null 2>&1 || exit 1`80- Provide portable implementations for missing utilities81- Use `[ -e "$file" ]` for existence checks (works on all systems)82- Avoid `/dev/stdin`, `/dev/stdout` (not universally available)83- Use explicit redirection instead of `&>` (bash-specific)8485## Readability & Maintainability8687- Use descriptive variable names in UPPER_CASE for exports, lower_case for locals88- Add section headers with comment blocks for organization89- Keep functions under 50 lines; extract complex logic90- Use consistent indentation (spaces only, typically 2 or 4)91- Document function purpose and parameters in comments92- Use meaningful names: `validate_input` not `check`93- Add comments for non-obvious POSIX workarounds94- Group related functions with descriptive headers95- Extract repeated code into functions96- Use blank lines to separate logical sections9798## Safety & Security Patterns99100- Quote all variable expansions to prevent word splitting101- Validate file permissions before operations: `[ -r "$file" ] || exit 1`102- Sanitize user input before using in commands103- Validate numeric input: `case $num in *[!0-9]*) exit 1 ;; esac`104- Never use `eval` on untrusted input105- Use `--` to separate options from arguments: `rm -- "$file"`106- Validate required variables: `[ -n "$VAR" ] || { echo "VAR required" >&2; exit 1; }`107- Check exit codes explicitly: `cmd || { echo "failed" >&2; exit 1; }`108- Use `trap` for cleanup: `trap 'rm -f "$tmpfile"' EXIT INT TERM`109- Set restrictive umask for sensitive files: `umask 077`110- Log security-relevant operations to syslog or file111- Validate file paths don't contain unexpected characters112- Use full paths for commands in security-critical scripts: `/bin/rm` not `rm`113114## Performance Optimization115116- Use shell built-ins over external commands when possible117- Avoid spawning subshells in loops: use `while read` not `for i in $(cat)`118- Cache command results in variables instead of repeated execution119- Use `case` for multiple string comparisons (faster than repeated `if`)120- Process files line-by-line for large files121- Use `expr` or `$(( ))` for arithmetic (POSIX supports `$(( ))`)122- Minimize external command calls in tight loops123- Use `grep -q` when you only need true/false (faster than capturing output)124- Batch similar operations together125- Use here-documents for multi-line strings instead of multiple echo calls126127## Documentation Standards128129- Implement `-h` flag for help (avoid `--help` without proper parsing)130- Include usage message showing synopsis and options131- Document required vs optional arguments clearly132- List exit codes: 0=success, 1=error, specific codes for specific failures133- Document prerequisites and required commands134- Add header comment with script purpose and author135- Include examples of common usage patterns136- Document environment variables used by script137- Provide troubleshooting guidance for common issues138- Note POSIX compliance in documentation139140## Working Without Arrays141142Since POSIX sh lacks arrays, use these patterns:143144- **Positional Parameters**: `set -- item1 item2 item3; for arg; do echo "$arg"; done`145- **Delimited Strings**: `items="a:b:c"; IFS=:; set -- $items; IFS=' '`146- **Newline-Separated**: `items="a\nb\nc"; while IFS= read -r item; do echo "$item"; done <<EOF`147- **Counters**: `i=0; while [ $i -lt 10 ]; do i=$((i+1)); done`148- **Field Splitting**: Use `cut`, `awk`, or parameter expansion for string splitting149150## Portable Conditionals151152Use `[ ]` test command with POSIX operators:153154- **File Tests**: `[ -e file ]` exists, `[ -f file ]` regular file, `[ -d dir ]` directory155- **String Tests**: `[ -z "$str" ]` empty, `[ -n "$str" ]` not empty, `[ "$a" = "$b" ]` equal156- **Numeric Tests**: `[ "$a" -eq "$b" ]` equal, `[ "$a" -lt "$b" ]` less than157- **Logical**: `[ cond1 ] && [ cond2 ]` AND, `[ cond1 ] || [ cond2 ]` OR158- **Negation**: `[ ! -f file ]` not a file159- **Pattern Matching**: Use `case` not `[[ =~ ]]`160161## CI/CD Integration162163- **Matrix testing**: Test across dash, ash, bash --posix, yash on Linux, macOS, Alpine164- **Container testing**: Use alpine:latest (ash), debian:stable (dash) for reproducible tests165- **Pre-commit hooks**: Configure checkbashisms, shellcheck -s sh, shfmt -ln posix166- **GitHub Actions**: Use shellcheck-problem-matchers with POSIX mode167- **Cross-platform validation**: Test on Linux, macOS, FreeBSD, NetBSD168- **BusyBox testing**: Validate on BusyBox environments for embedded systems169- **Automated releases**: Tag versions and generate portable distribution packages170- **Coverage tracking**: Ensure test coverage across all POSIX shells171- Example workflow: `shellcheck -s sh *.sh && shfmt -ln posix -d *.sh && checkbashisms *.sh`172173## Embedded Systems & Limited Environments174175- **BusyBox compatibility**: Test with BusyBox's limited ash implementation176- **Alpine Linux**: Default shell is BusyBox ash, not bash177- **Resource constraints**: Minimize memory usage, avoid spawning excessive processes178- **Missing utilities**: Provide fallbacks when common tools unavailable (`mktemp`, `seq`)179- **Read-only filesystems**: Handle scenarios where `/tmp` may be restricted180- **No coreutils**: Some environments lack GNU coreutils extensions181- **Signal handling**: Limited signal support in minimal environments182- **Startup scripts**: Init scripts must be POSIX for maximum compatibility183- Example: Check for mktemp: `command -v mktemp >/dev/null 2>&1 || mktemp() { ... }`184185## Migration from Bash to POSIX sh186187- **Assessment**: Run `checkbashisms` to identify bash-specific constructs188- **Array elimination**: Convert arrays to delimited strings or positional parameters189- **Conditional updates**: Replace `[[` with `[` and adjust regex to `case` patterns190- **Local variables**: Remove `local` keyword, use function prefixes instead191- **Process substitution**: Replace `<()` with temporary files or pipes192- **Parameter expansion**: Use `sed`/`awk` for complex string manipulation193- **Testing strategy**: Incremental conversion with continuous validation194- **Documentation**: Note any POSIX limitations or workarounds195- **Gradual migration**: Convert one function at a time, test thoroughly196- **Fallback support**: Maintain dual implementations during transition if needed197198## Quality Checklist199200- Scripts pass ShellCheck with `-s sh` flag (POSIX mode)201- Code is formatted consistently with shfmt using `-ln posix`202- Test on multiple shells: dash, ash, bash --posix, yash203- All variable expansions are properly quoted204- No bash-specific features used (arrays, `[[`, `local`, etc.)205- Error handling covers all failure modes206- Temporary resources cleaned up with EXIT trap207- Scripts provide clear usage information208- Input validation prevents injection attacks209- Scripts portable across Unix-like systems (Linux, BSD, Solaris, macOS, Alpine)210- BusyBox compatibility validated for embedded use cases211- No GNU-specific extensions or flags used212213## Output214215- POSIX-compliant shell scripts maximizing portability216- Test suites using shellspec or bats-core validating across dash, ash, yash217- CI/CD configurations for multi-shell matrix testing218- Portable implementations of common patterns with fallbacks219- Documentation on POSIX limitations and workarounds with examples220- Migration guides for converting bash scripts to POSIX sh incrementally221- Cross-platform compatibility matrices (Linux, BSD, macOS, Solaris, Alpine)222- Performance benchmarks comparing different POSIX shells223- Fallback implementations for missing utilities (mktemp, seq, timeout)224- BusyBox-compatible scripts for embedded and container environments225- Package distributions for various platforms without bash dependency226227## Essential Tools228229### Static Analysis & Formatting230- **ShellCheck**: Static analyzer with `-s sh` for POSIX mode validation231- **shfmt**: Shell formatter with `-ln posix` option for POSIX syntax232- **checkbashisms**: Detects bash-specific constructs in scripts (from devscripts)233- **Semgrep**: SAST with POSIX-specific security rules234- **CodeQL**: Security scanning for shell scripts235236### POSIX Shell Implementations for Testing237- **dash**: Debian Almquist Shell - lightweight, strict POSIX compliance (primary test target)238- **ash**: Almquist Shell - BusyBox default, embedded systems239- **yash**: Yet Another Shell - strict POSIX conformance validation240- **posh**: Policy-compliant Ordinary Shell - Debian policy compliance241- **osh**: Oil Shell - modern POSIX-compatible shell with better error messages242- **bash --posix**: GNU Bash in POSIX mode for compatibility testing243244### Testing Frameworks245- **bats-core**: Bash testing framework (works with POSIX sh)246- **shellspec**: BDD-style testing that supports POSIX sh247- **shunit2**: xUnit-style framework with POSIX sh support248- **sharness**: Test framework used by Git (POSIX-compatible)249250## Common Pitfalls to Avoid251252- Using `[[` instead of `[` (bash-specific)253- Using arrays (not in POSIX sh)254- Using `local` keyword (bash/ksh extension)255- Using `echo` without `printf` (behavior varies across implementations)256- Using `source` instead of `.` for sourcing scripts257- Using bash-specific parameter expansion: `${var//pattern/replacement}`258- Using process substitution `<()` or `>()`259- Using `function` keyword (ksh/bash syntax)260- Using `$RANDOM` variable (not in POSIX)261- Using `read -a` for arrays (bash-specific)262- Using `set -o pipefail` (bash-specific)263- Using `&>` for redirection (use `>file 2>&1`)264265## Advanced Techniques266267- **Error Trapping**: `trap 'echo "Error at line $LINENO" >&2; exit 1' EXIT; trap - EXIT` on success268- **Safe Temp Files**: `tmpfile=$(mktemp) || exit 1; trap 'rm -f "$tmpfile"' EXIT INT TERM`269- **Simulating Arrays**: `set -- item1 item2 item3; for arg; do process "$arg"; done`270- **Field Parsing**: `IFS=:; while read -r user pass uid gid; do ...; done < /etc/passwd`271- **String Replacement**: `echo "$str" | sed 's/old/new/g'` or use parameter expansion `${str%suffix}`272- **Default Values**: `value=${var:-default}` assigns default if var unset or null273- **Portable Functions**: Avoid `function` keyword, use `func_name() { ... }`274- **Subshell Isolation**: `(cd dir && cmd)` changes directory without affecting parent275- **Here-documents**: `cat <<'EOF'` with quotes prevents variable expansion276- **Command Existence**: `command -v cmd >/dev/null 2>&1 && echo "found" || echo "missing"`277278## POSIX-Specific Best Practices279280- Always quote variable expansions: `"$var"` not `$var`281- Use `[ ]` with proper spacing: `[ "$a" = "$b" ]` not `["$a"="$b"]`282- Use `=` for string comparison, not `==` (bash extension)283- Use `.` for sourcing, not `source`284- Use `printf` for all output, avoid `echo -e` or `echo -n`285- Use `$(( ))` for arithmetic, not `let` or `declare -i`286- Use `case` for pattern matching, not `[[ =~ ]]`287- Test scripts with `sh -n script.sh` to check syntax288- Use `command -v` not `type` or `which` for portability289- Explicitly handle all error conditions with `|| exit 1`290291## References & Further Reading292293### POSIX Standards & Specifications294- [POSIX Shell Command Language](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html) - Official POSIX.1-2024 specification295- [POSIX Utilities](https://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html) - Complete list of POSIX-mandated utilities296- [Autoconf Portable Shell Programming](https://www.gnu.org/software/autoconf/manual/autoconf.html#Portable-Shell) - Comprehensive portability guide from GNU297298### Portability & Best Practices299- [Rich's sh (POSIX shell) tricks](http://www.etalabs.net/sh_tricks.html) - Advanced POSIX shell techniques300- [Suckless Shell Style Guide](https://suckless.org/coding_style/) - Minimalist POSIX sh patterns301- [FreeBSD Porter's Handbook - Shell](https://docs.freebsd.org/en/books/porters-handbook/makefiles/#porting-shlibs) - BSD portability considerations302303### Tools & Testing304- [checkbashisms](https://manpages.debian.org/testing/devscripts/checkbashisms.1.en.html) - Detect bash-specific constructs305
Full transparency — inspect the skill content before installing.