| name | shell-portability |
| user-invocable | false |
| description | Use when writing shell scripts that need to run across different systems, shells, or environments. Covers POSIX compatibility and platform differences. |
| allowed-tools | ["Read","Write","Edit","Bash","Grep","Glob"] |
Shell Script Portability
Techniques for writing shell scripts that work across different platforms and environments.
Shebang Selection
Bash Scripts
#!/usr/bin/env bash
POSIX Shell Scripts
#!/bin/sh
Bash vs POSIX Differences
Arrays (Bash only)
declare -a items=("one" "two" "three")
for item in "${items[@]}"; do
echo "$item"
done
set -- one two three
for item in "$@"; do
echo "$item"
done
Test Syntax
if [[ "$var" == "value" ]]; then
echo "match"
fi
if [ "$var" = "value" ]; then
echo "match"
fi
String Operations
if [[ "$input" =~ ^[0-9]+$ ]]; then
echo "numeric"
fi
case "$input" in
*[!0-9]*|'') echo "not numeric" ;;
*) echo "numeric" ;;
esac
Arithmetic
(( count++ ))
if (( count > 10 )); then
echo "greater"
fi
count=$((count + 1))
if [ "$count" -gt 10 ]; then
echo "greater"
fi
Platform Differences
macOS vs Linux
date -d "yesterday" +%Y-%m-%d
date -v-1d +%Y-%m-%d
if date --version >/dev/null 2>&1; then
yesterday=$(date -d "yesterday" +%Y-%m-%d)
else
yesterday=$(date -v-1d +%Y-%m-%d)
fi
sed Differences
sed -i 's/old/new/g' file.txt
sed -i '' 's/old/new/g' file.txt
sed 's/old/new/g' file.txt > file.txt.tmp && mv file.txt.tmp file.txt
sed_inplace() {
if sed --version >/dev/null 2>&1; then
sed -i "$@"
else
sed -i '' "$@"
fi
}
readlink Differences
readlink -f /path/to/link
resolve_path() {
local path="$1"
if command -v greadlink >/dev/null 2>&1; then
greadlink -f "$path"
elif command -v realpath >/dev/null 2>&1; then
realpath "$path"
else
cd "$(dirname "$path")" && pwd -P
fi
}
Detecting Environment
Operating System
detect_os() {
case "$(uname -s)" in
Linux*) echo "linux" ;;
Darwin*) echo "macos" ;;
MINGW*|CYGWIN*|MSYS*) echo "windows" ;;
FreeBSD*) echo "freebsd" ;;
*) echo "unknown" ;;
esac
}
OS=$(detect_os)
case "$OS" in
linux) INSTALL_CMD="apt-get install" ;;
macos) INSTALL_CMD="brew install" ;;
esac
Architecture
detect_arch() {
case "$(uname -m)" in
x86_64|amd64) echo "amd64" ;;
aarch64|arm64) echo "arm64" ;;
armv7l) echo "arm" ;;
*) echo "unknown" ;;
esac
}
Shell Detection
detect_shell() {
if [ -n "$BASH_VERSION" ]; then
echo "bash"
elif [ -n "$ZSH_VERSION" ]; then
echo "zsh"
else
echo "sh"
fi
}
Portable Patterns
Reading Files
while IFS= read -r line || [ -n "$line" ]; do
echo "$line"
done < "$file"
Temporary Files
make_temp() {
if command -v mktemp >/dev/null 2>&1; then
mktemp
else
local tmp="/tmp/tmp.$$.$RANDOM"
touch "$tmp" && echo "$tmp"
fi
}
Command Existence Check
has_command() {
command -v "$1" >/dev/null 2>&1
}
if has_command curl; then
curl "$url"
elif has_command wget; then
wget -O- "$url"
else
echo "No HTTP client available" >&2
exit 1
fi
String Contains
contains() {
case "$1" in
*"$2"*) return 0 ;;
*) return 1 ;;
esac
}
if contains "$PATH" "/usr/local/bin"; then
echo "Found in PATH"
fi
ShellCheck Compatibility
Disabling Warnings for Portability
if [[ "$var" =~ regex ]]; then
:
fi
echo 'Use $HOME for home directory'
Testing Multiple Shells
#!/usr/bin/env bash
Best Practices
- Choose the right shebang for your needs
- Document shell requirements in README
- Use
#!/usr/bin/env bash for bash scripts
- Test on multiple platforms when possible
- Prefer POSIX features when portability matters
- Abstract platform differences into functions
- Use ShellCheck with appropriate shell directive
- Provide fallbacks for platform-specific commands