Agent skill
shell-portability
Use when writing shell scripts that need to run across different systems, shells, or environments. Covers POSIX compatibility and platform differences.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/shell-portability
SKILL.md
Shell Script Portability
Techniques for writing shell scripts that work across different platforms and environments.
Shebang Selection
Bash Scripts
bash
#!/usr/bin/env bash
# Most portable for bash scripts
# Works on Linux, macOS, BSD
POSIX Shell Scripts
sh
#!/bin/sh
# For maximum portability
# Use only POSIX features
Bash vs POSIX Differences
Arrays (Bash only)
bash
# Bash - arrays available
declare -a items=("one" "two" "three")
for item in "${items[@]}"; do
echo "$item"
done
# POSIX - use positional parameters or space-separated strings
set -- one two three
for item in "$@"; do
echo "$item"
done
Test Syntax
bash
# Bash - extended test
if [[ "$var" == "value" ]]; then
echo "match"
fi
# POSIX - basic test
if [ "$var" = "value" ]; then
echo "match"
fi
String Operations
bash
# Bash - regex matching
if [[ "$input" =~ ^[0-9]+$ ]]; then
echo "numeric"
fi
# POSIX - use case or external tools
case "$input" in
*[!0-9]*|'') echo "not numeric" ;;
*) echo "numeric" ;;
esac
Arithmetic
bash
# Bash - arithmetic expansion
(( count++ ))
if (( count > 10 )); then
echo "greater"
fi
# POSIX - expr or arithmetic expansion
count=$((count + 1))
if [ "$count" -gt 10 ]; then
echo "greater"
fi
Platform Differences
macOS vs Linux
bash
# Date command differences
# GNU (Linux)
date -d "yesterday" +%Y-%m-%d
# BSD (macOS)
date -v-1d +%Y-%m-%d
# Portable approach
if date --version >/dev/null 2>&1; then
# GNU date
yesterday=$(date -d "yesterday" +%Y-%m-%d)
else
# BSD date
yesterday=$(date -v-1d +%Y-%m-%d)
fi
sed Differences
bash
# GNU sed - in-place edit
sed -i 's/old/new/g' file.txt
# BSD sed - requires backup extension
sed -i '' 's/old/new/g' file.txt
# Portable approach
sed 's/old/new/g' file.txt > file.txt.tmp && mv file.txt.tmp file.txt
# Or use a function
sed_inplace() {
if sed --version >/dev/null 2>&1; then
sed -i "$@"
else
sed -i '' "$@"
fi
}
readlink Differences
bash
# GNU readlink
readlink -f /path/to/link
# BSD/macOS - no -f option by default
# Use greadlink from coreutils or:
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
# Fallback
cd "$(dirname "$path")" && pwd -P
fi
}
Detecting Environment
Operating System
bash
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
bash
detect_arch() {
case "$(uname -m)" in
x86_64|amd64) echo "amd64" ;;
aarch64|arm64) echo "arm64" ;;
armv7l) echo "arm" ;;
*) echo "unknown" ;;
esac
}
Shell Detection
bash
detect_shell() {
if [ -n "$BASH_VERSION" ]; then
echo "bash"
elif [ -n "$ZSH_VERSION" ]; then
echo "zsh"
else
echo "sh"
fi
}
Portable Patterns
Reading Files
bash
# Portable line reading
while IFS= read -r line || [ -n "$line" ]; do
echo "$line"
done < "$file"
# The || [ -n "$line" ] handles files without trailing newline
Temporary Files
bash
# POSIX-compatible temp file
make_temp() {
if command -v mktemp >/dev/null 2>&1; then
mktemp
else
# Fallback
local tmp="/tmp/tmp.$$.$RANDOM"
touch "$tmp" && echo "$tmp"
fi
}
Command Existence Check
bash
# POSIX-compatible command check
has_command() {
command -v "$1" >/dev/null 2>&1
}
# Usage
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
bash
# POSIX-compatible string contains
contains() {
case "$1" in
*"$2"*) return 0 ;;
*) return 1 ;;
esac
}
# Usage
if contains "$PATH" "/usr/local/bin"; then
echo "Found in PATH"
fi
ShellCheck Compatibility
Disabling Warnings for Portability
bash
# When intentionally using non-portable features
# shellcheck disable=SC2039 # Bash-specific feature
if [[ "$var" =~ regex ]]; then
:
fi
# Document why
# shellcheck disable=SC2016 # Intentionally not expanding
echo 'Use $HOME for home directory'
Testing Multiple Shells
bash
#!/usr/bin/env bash
# shellcheck shell=bash
# Or for POSIX:
#!/bin/sh
# shellcheck shell=sh
Best Practices
- Choose the right shebang for your needs
- Document shell requirements in README
- Use
#!/usr/bin/env bashfor 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
Didn't find tool you were looking for?