C POSIX vs Bash Compatibility#
Understanding the differences between POSIX shell specification and Bash-specific features.
What is POSIX?#
POSIX (Portable Operating System Interface) defines a standard specification for Unix-like systems. The shell specification ensures scripts work across different Unix implementations: Bash, sh, dash, ksh, zsh, etc.
Key Principle: POSIX shell features work everywhere; Bash extensions only work in Bash.
When to Use Each#
Use POSIX Shell When:#
Script needs to run on multiple Unix systems
Running in containers with minimal
/bin/shWriting system administration scripts
Distributing code to diverse environments
Guaranteed to be simple operations
Use Bash When:#
Target system definitely has Bash
Need advanced features for complex scripts
Building interactive user scripts
System is Linux-based
Need performance features Bash provides
POSIX vs Bash Features#
1. Shebang Line#
POSIX Compatible:
#!/bin/sh
Bash Specific:
#!/bin/bash
2. Variable Declaration#
POSIX:
# Simple variable assignment
VAR="value"
# Cannot use arrays (not in POSIX)
# Cannot use associative arrays
Bash Specific:
# Indexed arrays
arr=(a b c)
${arr[0]}
${arr[@]}
# Associative arrays
declare -A map
map[key]="value"
3. Arithmetic Expansion#
POSIX:
# Use expr command (slower)
result=$(expr 5 + 3)
result=$(expr $x \* 2) # Note: * must be escaped
Bash (much faster):
result=$((5 + 3))
result=$((x * 2))
result=$((x**2)) # Exponentiation (Bash only)
4. String Operations#
POSIX:
# Limited to parameter expansion
${VAR#pattern} # Remove shortest prefix
${VAR%pattern} # Remove shortest suffix
${VAR:-default} # Use default if unset
# No length, substring, or case conversion
Bash Specific:
# Additional operations
${#VAR} # Length
${VAR:0:5} # Substring
${VAR/find/replace} # Replace
${VAR//find/replace} # Replace all (Bash 4+)
${VAR^^} # Uppercase (Bash 4+)
${VAR,,} # Lowercase (Bash 4+)
${!VAR@} # Variable names matching prefix
5. Test Operators#
POSIX:
[ -f FILE ] # File test
[ -d DIR ] # Directory test
[ STRING = STRING ] # String comparison
[ -n STRING ] # Non-empty string
[ -z STRING ] # Empty string
[ NUM -eq NUM ] # Numeric equal
[ NUM -ne NUM ] # Numeric not equal
[ NUM -lt NUM ] # Numeric less than
[ -a COND ] # AND operator
[ -o COND ] # OR operator
[ ! COND ] # NOT operator
Bash Specific (using [[ ]]):
[[ -f FILE ]]
[[ STRING = PATTERN ]] # Glob pattern matching
[[ STRING =~ REGEX ]] # Regular expression matching
[[ COND && COND ]] # AND operator (cleaner)
[[ COND || COND ]] # OR operator (cleaner)
[[ ! COND ]] # NOT operator (cleaner)
6. Conditionals#
POSIX:
if CONDITION; then
COMMANDS
elif CONDITION; then
COMMANDS
else
COMMANDS
fi
case $VAR in
pattern) COMMANDS ;;
*) DEFAULT ;;
esac
Bash (same syntax, but more robust [[]]):
if [[ CONDITION ]]; then
COMMANDS
elif [[ CONDITION ]]; then
COMMANDS
else
COMMANDS
fi
# [[ ]] is safer (no word splitting, no glob expansion)
7. Loops#
POSIX (both work in Bash):
for VAR in LIST; do
COMMANDS
done
while CONDITION; do
COMMANDS
done
until CONDITION; do
COMMANDS
done
Bash Extensions:
# C-style for loop
for ((i=0; i<10; i++)); do
echo $i
done
# Brace expansion for loops
for i in {1..10}; do
echo $i
done
8. Functions#
POSIX:
FNAME() {
COMMANDS
}
# Call like
FNAME arg1 arg2
# Access arguments
$1 $2 $3 $@ $# $?
Bash:
# Both styles work
function fname() {
COMMANDS
}
fname() {
COMMANDS
}
# Bash-specific: local variables
function test() {
local var="local"
return 42
}
9. Command Substitution#
POSIX (both styles):
result=`command`
result=$(command)
# Prefer $() - more readable, nestable
result=$(echo $(date))
Note: Bash prefers $() syntax.
10. Redirection#
POSIX:
> FILE # Redirect stdout
< FILE # Redirect stdin
>> FILE # Append stdout
2> FILE # Redirect stderr (in POSIX)
2>&1 # Redirect stderr to stdout
| COMMAND # Pipe
Bash Additions:
&> FILE # Redirect both stdout and stderr (Bash only)
>>FILE 2>&1 # Append both (more portable)
|& COMMAND # Pipe both (Bash 4+)
11. Process Substitution#
POSIX: Not available
Bash Only:
while read line; do
process "$line"
done < <(command) # Redirect process output
paste <(seq 1 5) <(seq 1 5) # Side-by-side output
# Compare files
diff <(sort file1) <(sort file2)
12. Globbing and Expansion#
POSIX:
# Basic glob patterns
*.txt
?
[abc]
[a-z]
Bash Extensions:
# Brace expansion
{a,b,c}
{1..10}
{01..10} # With padding
# Extended glob patterns (shopt -s extglob)
!(pattern) # Anything except pattern
?(pattern) # Zero or one
*(pattern) # Zero or more
+(pattern) # One or more
13. Named Parameters#
POSIX: Not available (positional only)
Bash 4+ Specific:
function greet() {
# Still not true named params, but use patterns
local name="${1:?Name required}"
local greeting="${2:-Hello}"
echo "$greeting, $name"
}
# Call like
greet "John" "Hi"
# Workaround for named params
function deploy() {
while [[ $# -gt 0 ]]; do
case $1 in
--env=*) ENV="${1#*=}" ;;
--tag=*) TAG="${1#*=}" ;;
*) echo "Unknown: $1" ;;
esac
shift
done
}
deploy --env=prod --tag=v1.0
Compatibility Checklist#
Writing Portable Scripts#
Use this checklist to ensure POSIX compatibility:
Use
#!/bin/sh(not#!/bin/bash)Avoid
[[ ]]- use[ ]ortestAvoid
${var:0:5}substring - not POSIXAvoid
${var//find/replace}- usesedAvoid
declare -aarrays - use separate variables orsetUse
exprfor arithmetic (or accept Bash-only)Use
`cmd`or$(cmd)not just variablesAvoid function-local variables - use
localonly if Bash-onlyDon’t use
+=operator - not POSIXAvoid process substitution
<(cmd)Test with
shnot justbash
Conditional POSIX/Bash Code#
#!/bin/bash
# Works in both, but uses Bash when available
# POSIX way to get array length (compatible)
length=${#arr}
# Bash way (better in Bash)
if [[ -n "${BASH_VERSION}" ]]; then
length=${#arr[@]}
else
length=${#arr}
fi
# Check for Bash features
if command -v shopt &> /dev/null; then
# This is Bash
shopt -s extglob
fi
Common Incompatibilities#
Feature |
POSIX |
Bash |
Issue |
|---|---|---|---|
|
No |
Yes |
Use |
Arrays |
No |
Yes |
Use separate variables |
Associative arrays |
No |
Yes |
Bash 4+ only |
|
No |
Yes |
Use |
|
No |
Yes |
Not in POSIX |
|
No |
Yes |
Use |
|
No |
Yes |
Use |
Regex in |
No |
Yes |
Use |
|
No |
Yes |
Use |
|
Partial |
Yes |
Not guaranteed POSIX |
Process substitution |
No |
Yes |
Bash only feature |
|
No |
Yes |
Bash only |
Associative array |
No |
Yes |
Bash 4+ |
Testing Compatibility#
# Test in POSIX sh (dash is strict POSIX)
sh -n script.sh # Syntax check
sh script.sh # Run with sh
# Test with different shells
for shell in sh bash ksh zsh; do
echo "Testing with $shell:"
$shell -n script.sh
$shell script.sh
done
# Use ShellCheck to find compatibility issues
shellcheck -S warning script.sh
Migration Path#
From Bash-only to POSIX:#
Remove
[[ ]]→ use[ ]Remove arrays → use multiple variables or loops
Replace
expr→ keepexpr(not arithmetic)Remove substring syntax → use
cutorsedTest with
shregularly
From POSIX to Bash:#
Add
#!/bin/bashUse
[[ ]]for safetyUse
$((expr))for arithmeticUse arrays for complex data
Simplify with modern Bash features