跨平台Shell脚本兼容性Skill shell-portability

此技能专注于编写能在不同系统、Shell和环境间可移植的Shell脚本,涵盖POSIX兼容性、平台差异处理、环境检测和脚本优化等关键技术。旨在提高脚本的通用性、稳定性和维护性。关键词:Shell脚本、跨平台、POSIX兼容、环境适配、脚本可移植性、平台差异、ShellCheck。

DevOps 0 次安装 0 次浏览 更新于 3/25/2026

名称:shell-portability 用户可调用:false 描述:适用于编写需要在不同系统、Shell或环境中运行的Shell脚本。涵盖POSIX兼容性和平台差异。 允许的工具:

  • 读取
  • 写入
  • 编辑
  • Bash
  • Grep
  • Glob

Shell脚本可移植性

编写在不同平台和环境间工作的Shell脚本的技术。

Shebang选择

Bash脚本

#!/usr/bin/env bash
# 对于Bash脚本最可移植
# 在Linux、macOS、BSD上工作

POSIX Shell脚本

#!/bin/sh
# 为了最大可移植性
# 仅使用POSIX特性

Bash与POSIX差异

数组(仅Bash)

# Bash - 数组可用
declare -a items=("一" "二" "三")
for item in "${items[@]}"; do
    echo "$item"
done

# POSIX - 使用位置参数或空格分隔的字符串
set -- 一 二 三
for item in "$@"; do
    echo "$item"
done

测试语法

# Bash - 扩展测试
if [[ "$var" == "值" ]]; then
    echo "匹配"
fi

# POSIX - 基本测试
if [ "$var" = "值" ]; then
    echo "匹配"
fi

字符串操作

# Bash - 正则表达式匹配
if [[ "$input" =~ ^[0-9]+$ ]]; then
    echo "数字"
fi

# POSIX - 使用case或外部工具
case "$input" in
    *[!0-9]*|'') echo "非数字" ;;
    *) echo "数字" ;;
esac

算术

# Bash - 算术扩展
(( count++ ))
if (( count > 10 )); then
    echo "大于"
fi

# POSIX - expr或算术扩展
count=$((count + 1))
if [ "$count" -gt 10 ]; then
    echo "大于"
fi

平台差异

macOS与Linux

# 日期命令差异
# GNU(Linux)
date -d "昨天" +%Y-%m-%d

# BSD(macOS)
date -v-1d +%Y-%m-%d

# 可移植方法
if date --version >/dev/null 2>&1; then
    # GNU日期
    yesterday=$(date -d "昨天" +%Y-%m-%d)
else
    # BSD日期
    yesterday=$(date -v-1d +%Y-%m-%d)
fi

sed差异

# GNU sed - 原地编辑
sed -i 's/旧/新/g' file.txt

# BSD sed - 需要备份扩展名
sed -i '' 's/旧/新/g' file.txt

# 可移植方法
sed 's/旧/新/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差异

# GNU readlink
readlink -f /path/to/link

# BSD/macOS - 默认无-f选项
# 使用coreutils的greadlink或:
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
}

检测环境

操作系统

detect_os() {
    case "$(uname -s)" in
        Linux*)  echo "linux" ;;
        Darwin*) echo "macos" ;;
        MINGW*|CYGWIN*|MSYS*) echo "windows" ;;
        FreeBSD*) echo "freebsd" ;;
        *) echo "未知" ;;
    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 "未知" ;;
    esac
}

Shell检测

detect_shell() {
    if [ -n "$BASH_VERSION" ]; then
        echo "bash"
    elif [ -n "$ZSH_VERSION" ]; then
        echo "zsh"
    else
        echo "sh"
    fi
}

可移植模式

读取文件

# 可移植行读取
while IFS= read -r line || [ -n "$line" ]; do
    echo "$line"
done < "$file"
# || [ -n "$line" ] 处理无尾随换行符的文件

临时文件

# POSIX兼容的临时文件
make_temp() {
    if command -v mktemp >/dev/null 2>&1; then
        mktemp
    else
        # 回退
        local tmp="/tmp/tmp.$$.$RANDOM"
        touch "$tmp" && echo "$tmp"
    fi
}

命令存在性检查

# POSIX兼容的命令检查
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 "无HTTP客户端可用" >&2
    exit 1
fi

字符串包含

# POSIX兼容的字符串包含
contains() {
    case "$1" in
        *"$2"*) return 0 ;;
        *) return 1 ;;
    esac
}

# 使用
if contains "$PATH" "/usr/local/bin"; then
    echo "在PATH中找到"
fi

ShellCheck兼容性

禁用警告以实现可移植性

# 当有意使用非可移植特性时
# shellcheck disable=SC2039  # Bash特定特性
if [[ "$var" =~ regex ]]; then
    :
fi

# 文档说明原因
# shellcheck disable=SC2016  # 有意不扩展
echo '使用$HOME表示主目录'

测试多个Shell

#!/usr/bin/env bash
# shellcheck shell=bash

# 或对于POSIX:
#!/bin/sh
# shellcheck shell=sh

最佳实践

  1. 根据需要选择合适的shebang
  2. 在README中记录Shell要求
  3. 对于Bash脚本,使用#!/usr/bin/env bash
  4. 尽可能在多个平台上测试
  5. 当可移植性重要时,优先使用POSIX特性
  6. 将平台差异抽象为函数
  7. 使用ShellCheck并指定适当的shell指令
  8. 为平台特定命令提供回退