shell-portability

Shell Script Portability

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "shell-portability" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-shell-portability

Shell Script Portability

Techniques for writing shell scripts that work across different platforms and environments.

Shebang Selection

Bash Scripts

#!/usr/bin/env bash

Most portable for bash scripts

Works on Linux, macOS, BSD

POSIX Shell Scripts

#!/bin/sh

For maximum portability

Use only POSIX features

Bash vs POSIX Differences

Arrays (Bash only)

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 - extended test

if [[ "$var" == "value" ]]; then echo "match" fi

POSIX - basic test

if [ "$var" = "value" ]; then echo "match" fi

String Operations

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 - 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

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

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

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

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

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

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

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

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

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

#!/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 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

storybook-story-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

atomic-design-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review