From shellcheck
Guides writing portable shell scripts across platforms like Linux/macOS and shells like Bash/POSIX sh. Covers shebangs, command differences (date/sed/readlink), and environment detection.
How this skill is triggered — by the user, by Claude, or both
Slash command
/shellcheck:shell-portabilityThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Techniques for writing shell scripts that work across different platforms and environments.
Techniques for writing shell scripts that work across different platforms and environments.
#!/usr/bin/env bash
# Most portable for bash scripts
# Works on Linux, macOS, BSD
#!/bin/sh
# For maximum portability
# Use only POSIX features
# 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
# Bash - extended test
if [[ "$var" == "value" ]]; then
echo "match"
fi
# POSIX - basic test
if [ "$var" = "value" ]; then
echo "match"
fi
# 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
# 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
# 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
# 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
}
# 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
}
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
detect_arch() {
case "$(uname -m)" in
x86_64|amd64) echo "amd64" ;;
aarch64|arm64) echo "arm64" ;;
armv7l) echo "arm" ;;
*) echo "unknown" ;;
esac
}
detect_shell() {
if [ -n "$BASH_VERSION" ]; then
echo "bash"
elif [ -n "$ZSH_VERSION" ]; then
echo "zsh"
else
echo "sh"
fi
}
# Portable line reading
while IFS= read -r line || [ -n "$line" ]; do
echo "$line"
done < "$file"
# The || [ -n "$line" ] handles files without trailing newline
# 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
}
# 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
# 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
# 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'
#!/usr/bin/env bash
# shellcheck shell=bash
# Or for POSIX:
#!/bin/sh
# shellcheck shell=sh
#!/usr/bin/env bash for bash scriptsnpx claudepluginhub thebushidocollective/han --plugin shellcheckGuides writing portable POSIX-compatible shell scripts with shebang selection, bashism avoidance via feature matrix, and POSIX patterns for conditionals, case conversion, substrings, and file reading.
Guides writing strict POSIX sh scripts for maximum portability across Unix-like systems. Covers constraints, defensive programming, and cross-shell compatibility.
Shell script conventions, defensive patterns, and correctness rules: strict mode, quoting, portability, error handling, and common pitfalls. Invoke whenever task involves any interaction with shell scripts — writing, reviewing, debugging, or understanding .sh, .bash, .zsh files.