📜 POSIX Shell Reference
POSIX (Portable Operating System Interface) defines the standard for Unix-like operating systems, including the shell and utilities. Understanding POSIX shell requirements is crucial for writing portable scripts that work across different systems.
🎯 POSIX Shell Requirements
What is POSIX Shell?
POSIX shell is a standardized specification that defines:
- Shell command language syntax and semantics
- Built-in utilities and their behavior
- Environment variable conventions
- File system interface standards
- Process control mechanisms
POSIX Compliant Shells
1
2
3
4
5
6
7
8
9
10
11
12 | # Standard POSIX shells:
# - sh (Bourne shell)
# - dash (Debian Almquist Shell)
# - ash (Almquist Shell)
# - ksh (KornShell) in POSIX mode
# - bash in POSIX mode (bash --posix)
# - zsh in POSIX mode (zsh --emulate sh)
# Check if shell is POSIX compliant
if [ -n "$POSIXLY_CORRECT" ] || [ "$(basename "$0")" = "sh" ]; then
echo "Running in POSIX mode"
fi
|
🔧 POSIX Shell Syntax
Variable Assignment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | # ✅ POSIX compliant variable assignment
variable=value
export VARIABLE=value
readonly CONSTANT=value
# ❌ Not POSIX compliant
array=(item1 item2) # Arrays not supported
local var=value # 'local' not in POSIX
variable+=value # += operator not in POSIX
# ✅ POSIX alternatives
# For arrays, use delimited strings:
ITEMS="item1:item2:item3"
IFS=':' read -ra item_array <<< "$ITEMS"
# For local variables:
var=value # Just use regular variables in functions
# For string appending:
variable="${variable}value"
|
Conditional Expressions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | # ✅ POSIX compliant tests
if [ "$var" = "value" ]; then
echo "Equal"
fi
if [ "$num" -eq 5 ]; then
echo "Equal to 5"
fi
if [ -f "$file" ]; then
echo "File exists"
fi
# ❌ Not POSIX compliant
if [[ "$var" == pattern ]]; then # [[ ]] not in POSIX
if [ "$var" =~ regex ]; then # =~ not in POSIX
if (( num > 5 )); then # (( )) not in POSIX
# ✅ POSIX alternatives
# Pattern matching:
case "$var" in
pattern*)
echo "Matches pattern"
;;
esac
# Arithmetic comparison:
if [ "$((num))" -gt 5 ]; then
echo "Greater than 5"
fi
|
Command Substitution
1
2
3
4
5
6
7
8
9
10
11
12 | # ✅ POSIX compliant
result=$(command)
result=`command` # Backticks also work but less readable
# ✅ Nested command substitution
outer=$(command1 $(command2))
# ❌ Not POSIX compliant
result=$(command1; command2) # Command lists in $() may not be portable
# ✅ POSIX alternative
result=$(command1 && command2)
|
📋 POSIX Built-in Commands
Essential Built-ins
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | # ✅ Required POSIX built-ins:
# break, cd, continue, eval, exec, exit, export
# getopts, hash, pwd, readonly, return, shift
# test, times, trap, unset
# ✅ Standard utilities (must be available):
# basename, cat, chmod, cmp, cp, date, dd, df
# echo, expr, false, find, grep, kill, ln, ls
# mkdir, mv, ps, pwd, rm, rmdir, sed, sh
# sleep, sort, stty, sync, tar, touch, true
# uname, wc, who
# Example usage:
cd /path/to/directory
export PATH="/usr/local/bin:$PATH"
readonly CONFIG_FILE="/etc/myapp.conf"
shift 2 # Shift positional parameters
|
Test Command Variants
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | # ✅ POSIX compliant test syntax
[ "$var" = "value" ] # String equality
[ "$num" -eq 5 ] # Numeric equality
[ -f "$file" ] # File exists and is regular file
[ -d "$dir" ] # Directory exists
[ -r "$file" ] # File is readable
[ -w "$file" ] # File is writable
[ -x "$file" ] # File is executable
[ "$str1" != "$str2" ] # String inequality
[ "$num1" -lt "$num2" ] # Less than
[ "$num1" -gt "$num2" ] # Greater than
[ "$num1" -le "$num2" ] # Less than or equal
[ "$num1" -ge "$num2" ] # Greater than or equal
[ -z "$str" ] # String is zero length
[ -n "$str" ] # String is non-zero length
# ❌ Not POSIX compliant
[ "$var" == "value" ] # == not in POSIX (use =)
[ "$var" = value ] # Right side should be quoted
[ $num -eq 05 ] # Leading zeros may cause octal interpretation
|
🔄 Control Structures
Loops
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | # ✅ POSIX compliant loops
# For loop with word list
for var in word1 word2 word3; do
echo "$var"
done
# For loop with positional parameters
for arg in "$@"; do
echo "$arg"
done
# While loop
while [ "$condition" = "true" ]; do
# loop body
condition=$(get_new_condition)
done
# Until loop
until [ "$condition" = "false" ]; do
# loop body
condition=$(get_new_condition)
done
# ❌ Not POSIX compliant
for (( i=0; i<10; i++ )); do # C-style for loop not in POSIX
echo "$i"
done
# ✅ POSIX alternative for counting
i=0
while [ $i -lt 10 ]; do
echo "$i"
i=$((i + 1))
done
|
Case Statement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | # ✅ POSIX compliant case statement
case "$variable" in
pattern1)
echo "Matches pattern1"
;;
pattern2|pattern3)
echo "Matches pattern2 or pattern3"
;;
*)
echo "No match"
;;
esac
# ❌ Extended patterns not in POSIX
case "$var" in
!(pattern)) # Not in POSIX
;;
+(pattern)) # Not in POSIX
;;
esac
|
📊 I/O Redirection
Standard Redirection Operators
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | # ✅ POSIX compliant redirection
# Basic redirection
command > file.txt # Standard output to file
command < file.txt # Standard input from file
command 2> error.log # Standard error to file
command > output.log 2>&1 # Both stdout and stderr to file
command >> file.txt # Append to file
command 2>> error.log # Append error to file
# Here documents
cat << EOF
This is a here document
Variable expansion: $HOME
EOF
# Here documents with quoted delimiter (no expansion)
cat << 'EOF'
No variable expansion here: $HOME
EOF
# Here strings (limited support)
# Note: Here strings (<<<) are not in POSIX
# Use here documents instead:
command << EOF
single line input
EOF
# ❌ Not in POSIX
command <<< "input" # Here strings not POSIX
command &> file.txt # Combined redirect not POSIX
|
🛠️ Functions and Parameters
Function Definition
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | # ✅ POSIX compliant function definition
my_function() {
echo "Hello from function"
echo "Arguments: $@"
echo "First argument: $1"
echo "Number of arguments: $#"
return 0
}
# Call function
my_function arg1 arg2
# ❌ Not in POSIX
function my_function { # 'function' keyword not in POSIX
# function body
}
# ✅ Both forms work, but () is more portable
|
Parameter Expansion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | # ✅ POSIX compliant parameter expansion
var="hello world"
echo "$var" # hello world
echo "${var}" # hello world
echo "${var:-default}" # Use default if var is unset or empty
echo "${var:=default}" # Set and use default if var is unset or empty
echo "${var:+value}" # Use value if var is set and non-empty
echo "${var#pattern}" # Remove shortest match from beginning
echo "${var##pattern}" # Remove longest match from beginning
echo "${var%pattern}" # Remove shortest match from end
echo "${var%%pattern}" # Remove longest match from end
# ❌ Not in POSIX
echo "${var^^}" # Case conversion not in POSIX
echo "${var,,}" # Case conversion not in POSIX
echo "${var:0:5}" # Substring not in POSIX
echo "${#var}" # Length not in POSIX (but widely supported)
# ✅ POSIX alternatives for unsupported features
# Length:
expr length "$var" # Or: echo "$var" | wc -c
# Substring:
echo "$var" | cut -c1-5
# Case conversion:
echo "$var" | tr '[:lower:]' '[:upper:]'
|
🎨 Advanced POSIX Features
Signal Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | # ✅ POSIX compliant signal handling
trap 'echo "Caught SIGINT"' INT
trap 'echo "Caught SIGTERM"' TERM
trap 'echo "Script exiting"' EXIT
trap 'echo "Caught error"' ERR # Note: ERR trap not in POSIX
# List available signals
kill -l
# Send signals
kill -TERM 1234
kill -INT $$
# ❌ Not in POSIX
trap 'echo "Caught error"' ERR # ERR trap is bash-specific
|
Job Control
1
2
3
4
5
6
7
8
9
10
11
12 | # ✅ POSIX job control features
command & # Run in background
jobs # List jobs (may not be available)
fg %1 # Foreground job 1
bg %1 # Background job 1
kill %1 # Kill job 1
# Wait for background jobs
command1 &
command2 &
wait # Wait for all background jobs
wait $! # Wait for last background job
|
🔍 POSIX Compliance Testing
Testing Scripts for POSIX Compliance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 | # ✅ Test with POSIX shell
dash script.sh
# ✅ Use shellcheck with POSIX mode
shellcheck -s sh script.sh
# ✅ Check syntax with different shells
for shell in sh dash bash ksh; do
if command -v "$shell" >/dev/null 2>&1; then
echo "Testing with $shell..."
"$shell" -n script.sh || echo "Syntax error in $shell"
fi
done
# ✅ POSIX compliance checker
check_posix_compliance() {
local script="$1"
# Check for common non-POSIX constructs
if grep -q '\[\[' "$script"; then
echo "Warning: [[ ]] found (not POSIX)"
fi
if grep -q 'function ' "$script"; then
echo "Warning: 'function' keyword found (prefer POSIX syntax)"
fi
if grep -q '\(\(' "$script"; then
echo "Warning: (( )) found (not POSIX)"
fi
if grep -q '<<<' "$script"; then
echo "Warning: Here strings found (not POSIX)"
fi
# Test with dash
if command -v dash >/dev/null 2>&1; then
if ! dash -n "$script"; then
echo "Error: dash reports syntax errors"
fi
fi
}
|
POSIX Environment Variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | # ✅ Standard POSIX environment variables
echo "$HOME" # Home directory
echo "$PATH" # Command search path
echo "$PWD" # Current working directory
echo "$OLDPWD" # Previous working directory
echo "$SHELL" # Login shell
echo "$TERM" # Terminal type
echo "$USER" # Username
echo "$LOGNAME" # Login name
# ✅ POSIX special parameters
echo "$*" # All positional parameters as single string
echo "$@" # All positional parameters as separate strings
echo "$#" # Number of positional parameters
echo "$?" # Exit status of last command
echo "$$" # Process ID of current shell
echo "$!" # Process ID of last background command
echo "$0" # Name of current script
|
🧾 Summary Guidelines
POSIX Compliance Checklist
✅ Use #!/bin/sh shebang for maximum portability
✅ Quote all variable expansions: "$var"
✅ Use [ ] instead of [[ ]] for tests
✅ Avoid arrays (use delimited strings instead)
✅ Use standard parameter expansion only
✅ Avoid here strings (<<<)
✅ Use standard redirection operators only
✅ Test with multiple POSIX shells (dash, ash)
✅ Avoid shell-specific built-ins and keywords
When to Use POSIX Shell
✅ Use POSIX when:
- Writing portable scripts for multiple systems
- Targeting minimal environments (containers, embedded)
- Need maximum compatibility
- Following organizational standards
- Writing system initialization scripts
✅ Consider alternatives when:
- Need advanced features (arrays, associative arrays)
- Want better error handling and debugging
- Require interactive shell enhancements
- Need extensive pattern matching capabilities
🧠 Portable Script Template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | #!/bin/sh
# POSIX compliant script template
# Set strict mode (careful with this in some shells)
# set -e # Exit on error
# set -u # Exit on undefined variables
# Error handling function
error_exit() {
echo "Error: $1" >&2
exit "${2:-1}"
}
# Input validation
validate_required() {
# Check if variable is set
if [ -z "$(eval echo \$$1)" ]; then
error_exit "Required parameter $1 is not set"
fi
}
# Safe command execution
safe_execute() {
"$@" || error_exit "Command failed: $*"
}
# Main function
main() {
# Validate inputs
# validate_required "INPUT_FILE"
# Your logic here
echo "Running POSIX compliant script"
}
# Run main function
main "$@"
|
🧾 See Also