Przejdลบ do treล›ci

๐ŸŒ Extended Portability Patterns

Write scripts that work across different Unix-like systems without modification.

๐Ÿงญ Portability Principles

  1. Stick to POSIX โ€” Avoid Bashisms unless necessary
  2. Explicit dependencies โ€” Document required tools
  3. Graceful degradation โ€” Offer alternatives when features missing
  4. Test broadly โ€” Run on multiple shells and OSes

๐Ÿงช Writing Portable Code

Use POSIX Test Syntax

1
2
3
4
5
# โœ… Portable
if [ "$var" = "value" ]; then ...

# โŒ Bash-specific
if [[ "$var" == value ]]; then ...

Avoid Bash Arrays

1
2
3
4
5
6
7
8
# โŒ Bash-specific
items=("apple" "banana")
for item in "${items[@]}"; do ...

# โœ… Portable
set -- apple banana
for item; do ...
done

Or use positional parameters:

1
2
3
4
5
6
7
8
#!/bin/sh
process_items() {
    for item; do
        echo "Processing: $item"
    done
}

process_items apple banana cherry

๐Ÿง  Detect Shell Capabilities

Check for features at runtime:

1
2
3
4
5
6
# 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:

1
2
3
4
5
6
7
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

๐Ÿง  Handling Missing Tools

Check for Required Commands

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

1
2
3
4
for shell in sh dash bash zsh; do
    echo "Testing with $shell..."
    $shell ./myscript.sh || echo "Failed in $shell"
done

Use ShellCheck

1
2
3
4
5
6
7
# 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