| name | posix-shell |
| description | Skill for writing POSIX-compliant shell scripts. Covers portable sh/bash script creation, ensuring portability, avoiding common non-POSIX syntax, error handling, testable design, debugging techniques, etc. Always refer to this skill when keywords like "shell script", "sh", "bash", "POSIX", "shell functions", "shell variables", "trap", "getopts" are included. Use in all scenarios for writing, reviewing, and debugging scripts.
|
POSIX-Compliant Shell Scripting Skill
Basic Policy
All scripts assume execution under /bin/sh.
Strictly avoid Bash-specific features.
Shebang
#!/bin/sh
Only allow #!/usr/bin/env bash when intentionally using Bash-specific features.
In that case, add # requires: bash comment at the top.
Essential Safeguards
Always include the following at the script beginning:
#!/bin/sh
set -eu
IFS=$(printf '\n\t')
| Option | Meaning |
|---|
-e | Exit if command returns non-zero |
-u | Error on unset variable reference |
-o pipefail | Bash only. Catch pipeline errors |
POSIX sh does not support -o pipefail. See references/pipefail-alternatives.md.
POSIXLY_CORRECT
export POSIXLY_CORRECT=1
Setting POSIXLY_CORRECT=1 in the environment makes shells (dash, bash in POSIX mode, etc.) more strictly POSIX-compliant.
This helps catch non-portable constructs during development and testing.
Note: This is a shell setting, not a set option. Use export or prefix when invoking shell.
Non-POSIX Syntax (Never Use)
[[ ... ]]
(( n++ ))
${var,,}
${var^^}
local var=val
echo -e "..."
source file
function foo() {}
>&
read -r -d ''
Variables and Quotes
echo "$var"
rm -f "$file"
for f in "$@"; do
echo "$f"
done
name="${1:-world}"
val="${VAR:-default}"
: "${REQUIRED_VAR:?REQUIRED_VAR must be set}"
Conditionals
[ -z "$var" ]
[ -n "$var" ]
[ "$a" = "$b" ]
[ "$a" != "$b" ]
[ "$n" -eq 0 ]
[ -f "$file" ]
[ -d "$dir" ]
[ -r "$file" ]
[ -x "$file" ]
[ -f "$f" ] && [ -r "$f" ]
Function Definitions
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%dT%H:%M:%S')" "$*" >&2
}
die() {
log "ERROR: $*"
exit 1
}
parse_args() {
local flag
local value
flag="$1"
value="$2"
}
local var=val single-line init fails to catch cmdsub errors under -e.
Always split: local var; var=$(cmd)
Error Handling and trap
#!/bin/sh
set -eu
TMPFILE=""
cleanup() {
[ -n "$TMPFILE" ] && rm -f "$TMPFILE"
}
trap cleanup EXIT
trap 'exit 1' INT TERM
TMPFILE=$(mktemp)
Argument Parsing (getopts)
usage() {
cat <<EOF
Usage: $(basename "$0") [-v] [-o OUTPUT] FILE...
Options:
-v verbose mode
-o OUTPUT output file (default: stdout)
-h show this help
EOF
}
verbose=0
output=""
while getopts ":vo:h" opt; do
case "$opt" in
v) verbose=1 ;;
o) output="$OPTARG" ;;
h) usage; exit 0 ;;
:) die "Option -$OPTARG requires an argument" ;;
\?) die "Unknown option: -$OPTARG" ;;
esac
done
shift $((OPTIND - 1))
Here Documents
cat <<'EOF'
No variable expansion with single quotes
$VAR printed as-is
EOF
cat <<-EOF
Indented content
Tabs stripped
EOF
cat <<EOF
Hello, $name!
Today is $(date).
EOF
Text Processing Idioms
lower=$(printf '%s' "$str" | tr '[:upper:]' '[:lower:]')
trim() {
printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
}
count=$(wc -l < "$file")
while IFS= read -r line; do
printf 'Line: %s\n' "$line"
done < "$file"
some_command | while IFS= read -r line; do
printf '%s\n' "$line"
done
Path/File Operations
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
filename=$(basename "$path")
dir=$(dirname "$path")
stem=$(basename "$path" .txt)
tmp=$(mktemp)
tmpdir=$(mktemp -d)
Variable Expansion
Always expand variables with double-quotes and braces: "${var_name}".
Never use bare $var, "$var", or ${var} without surrounding double-quotes.
echo "${message}"
cp "${src_file}" "${dest_dir}/"
log_file="${output_dir}/app.log"
echo $message
echo "$message"
echo ${message}
This rule applies to:
- All variable references in commands and arguments
- Variable assignments on the right-hand side (
dst="${base_dir}/sub")
- Parameter expansions (
"${1:-default}", "${var%.*}")
- Command substitutions inside strings (
"${output_dir}/$(date +%Y%m%d)")
Exception: variables inside $(( )) arithmetic expressions do not require quotes or braces.
Debugging
set -x
set +x
sh -x ./script.sh
sh -n ./script.sh
shellcheck ./script.sh
Checklist
Before completion, verify:
References
references/pipefail-alternatives.md — Pipeline error detection without pipefail
references/portability-table.md — Command/syntax portability table
references/common-patterns.md — Common patterns (locks, logging, config loading, etc.)