๐ Extended Portability Patterns
Write scripts that work across different Unix-like systems without modification.
๐งญ Portability Principles
- Stick to POSIX โ Avoid Bashisms unless necessary
- Explicit dependencies โ Document required tools
- Graceful degradation โ Offer alternatives when features missing
- Test broadly โ Run on multiple shells and OSes
๐งช Writing Portable Code
Use POSIX Test Syntax
| # โ
Portable
if [ "$var" = "value" ]; then ...
# โ Bash-specific
if [[ "$var" == value ]]; then ...
|
Avoid Bash Arrays
| # โ Bash-specific
items=("apple" "banana")
for item in "${items[@]}"; do ...
# โ
Portable
set -- apple banana
for item; do ...
done
|
Or use positional parameters:
| #!/bin/sh
process_items() {
for item; do
echo "Processing: $item"
done
}
process_items apple banana cherry
|
๐ง Detect Shell Capabilities
Check for features at runtime:
| # Check if array support exists
if [ "$(echo "test" | grep -o . | wc -l)" -eq 4 ]; then
echo "Supports advanced features"
else
echo "Limited shell โ use POSIX mode"
fi
|
Detect shell type:
| case "$0" in
*/bash*) echo "Bash detected" ;;
*/zsh*) echo "Zsh detected" ;;
*/dash*) echo "Dash detected" ;;
*/sh*) echo "POSIX sh" ;;
*) echo "Unknown shell" ;;
esac
|
๐งช Portable Alternatives Table
| Feature |
Portable Way |
Bash-specific Way |
| String comparison |
[ "$a" = "$b" ] |
[[ "$a" == "$b" ]] |
| Regex matching |
case or grep |
[[ =~ ]] |
| Arrays |
$@ or delimited strings |
array=() |
| Arithmetic |
expr or $(( )) |
(( )) |
| Process substitution |
Temporary files |
<(command) |
| Function names |
func() |
function func {} |
| Source file |
. file |
source file |
Check for Required Commands
| require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "Error: $1 is required but not installed" >&2
exit 1
fi
}
require_command grep
require_command awk
require_command sed
|
Fallback Implementations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | # Portable version of basename
my_basename() {
case "$1" in
*/*) echo "${1##*/}" ;;
*) echo "$1" ;;
esac
}
# Portable version of dirname
my_dirname() {
case "$1" in
*/*) echo "${1%/*}" ;;
*) echo "." ;;
esac
}
|
๐งช Testing Portability
Test Under Multiple Shells
| for shell in sh dash bash zsh; do
echo "Testing with $shell..."
$shell ./myscript.sh || echo "Failed in $shell"
done
|
Use ShellCheck
| # Install
sudo apt install shellcheck # Debian/Ubuntu
brew install shellcheck # macOS
# Run
shellcheck --shell=sh myscript.sh
shellcheck --shell=posix myscript.sh
|
ShellCheck catches:
- Unquoted variables
- Bashisms in sh scripts
- Common pitfalls
- Portability issues
๐ง Conditional Bashisms
Use Bash features only when available:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | #!/bin/sh
# Portable part
config_file="${1:-/etc/default.conf}"
# Bash-specific part (only if running in Bash)
if [ -n "$BASH_VERSION" ]; then
# Use Bash arrays
servers=("web1" "web2" "db1")
for server in "${servers[@]}"; do
echo "Deploying to $server"
done
else
# POSIX fallback
for server in web1 web2 db1; do
echo "Deploying to $server"
done
fi
|
๐งพ Summary
- Prefer POSIX constructs for maximum compatibility.
- Avoid Bash/Zsh-specific features in library code.
- Use
shellcheck to catch portability issues.
- Test scripts under multiple shells.
- Provide fallbacks when advanced features aren't available.
- Document known incompatibilities clearly.
๐ Proceed to: Advanced Shell Architecture