各位读者朋友,大家好。
之前在实际开发中,经常遇到这样一个场景:同一份 Shell 脚本,在一台 Ubuntu 服务器上运行得好好的,换到本机 macOS 终端里就报错,或者输出结果对不上。网上搜了一圈,答案零零散散,今天说要改sed参数,明天说要换grep写法,始终没有一个完整的解释。后来真正理解了 POSIX 这套规范之后,再看这些问题,思路就清晰多了:不是某条命令的错,而是脚本原本就不够“POSIX 兼容”。
本文将围绕 POSIX 标准,系统讲清楚它是什么、为什么 Linux 和 macOS 的 Shell 脚本基础部分能通用,以及我们在写脚本时如何规避系统差异,做到“一次编写,多处运行”。内容从概念到实战,再到高频问题和最佳实践,适合刚接触 Shell 的新人,也适合已经在写自动化脚本、但经常被跨平台问题困扰的开发者。
1. POSIX 到底是什么
1.1 用一句话解释 POSIX
POSIX 的全称是 Portable Operating System Interface,中文可以理解为“可移植操作系统接口”。它是一组由 IEEE(电气和电子工程师协会)制定的标准,编号是 IEEE 1003,后来也被 ISO 采纳为国际标准。
简单说,POSIX 就是为了解决一个古老的问题:同一个程序,能不能在不同的操作系统上,用几乎相同的方式编译、运行、交互?
在 POSIX 出现之前,Unix 系统有很多变种,比如 AT&T Unix、BSD Unix,各家的系统调用、命令行参数、文件路径规则都有差异。程序员写一个工具,往往要针对不同系统写不同的版本。POSIX 的出现,就是给操作系统和应用程序之间画了一条统一的“线”:操作系统只要遵守这条线,应用程序只要面向这条线开发,兼容性就能得到保障。
1.2 POSIX 规范了什么
POSIX 覆盖的内容非常广,但对我们日常开发最有感知的,可以归纳为以下几类:
| 范畴 | 示例 |
|---|---|
| 系统调用与 C 语言 API | open()、read()、fork()、wait()等 |
| 命令行解释器(Shell) | 定义sh的基本语法和行为 |
| 基础命令与工具 | ls、grep、sed、awk、find等 |
| 环境变量与路径规则 | PATH、HOME、/tmp等约定 |
| 文件系统布局 | /bin、/usr/bin、/etc等目录的基本语义 |
| 信号与进程管理 | SIGINT、SIGTERM、进程退出码等 |
需要注意的是,POSIX 并不是一个“具体的操作系统”,它是一套标准。Linux 和 macOS 分别是“兼容 POSIX”的典型系统,但它们并不完全一致,也不等于“实现了所有 POSIX 内容就是同一个系统”。
1.3 为什么 Linux 和 macOS 都能沾上 POSIX
Linux 在设计之初就明确以 Unix 为蓝本,通过 GNU 工具集和 glibc 等实现,兼容 POSIX 标准以及更广泛的 Unix 习惯。
macOS 的底层是 Darwin,Darwin 的核心 XNU 内核包含了来自 BSD 的大量代码,同时也继承了 Unix 的系统调用和命令行环境,因此 macOS 后来也获得了 UNIX 03 认证,是官方认定的 Unix 系统。
正因为两者都遵循了同一套接口标准和命令行约定,所以我们在 Linux 上写的很多 Shell 脚本,拿到 macOS 上也能跑,或者只需要做很小的改动就能跑。这也是很多开源项目敢在 README 里写“支持 Linux / macOS”的底气来源。
2. 环境准备与版本说明
这一节我们先梳理一个可验证的环境。本文的示例在下面这套环境里测试过,你在自己机器上操作时,版本可能略有差异,但整体思路不变。
2.1 推荐环境
| 项目 | 版本建议 |
|---|---|
| Linux 发行版 | Ubuntu 22.04 LTS 或 CentOS Stream 9 |
| macOS | macOS 13 Ventura 及以上 |
| 默认 Shell | bash 5.x,同时验证 sh / dash |
| 代码编辑器 | VS Code 或任意终端编辑器 |
在 Linux 上,/bin/sh通常指向dash(Debian 系列)或bash(RHEL 系列);在 macOS 上,/bin/sh是 bash 的 POSIX 模式。这个差异非常影响脚本兼容性,后面会专门讲。
2.2 检查当前系统类型和 Shell
写可移植脚本前,先要学会观察运行环境。下面这几个命令,可以帮我们快速获取系统基本信息。
# 查看内核和系统类型 uname -s uname -r uname -m # 查看操作系统发行版信息(Linux) cat /etc/os-release # 查看当前 Shell echo "$SHELL" echo "$BASH_VERSION" # 查看 /bin/sh 指向哪里 ls -l /bin/sh在 Ubuntu 上,ls -l /bin/sh的输出可能是:
lrwxrwxrwx 1 root root 4 xx月 xx日 /bin/sh -> dash在 macOS 上则是:
lrwxr-xr-x 1 root wheel 5 某月某日 /bin/sh -> bash这说明同一个/bin/sh,在不同系统上背后是不同的解释器。如果你的脚本开头写的是#!/bin/sh,那么它就会跟随系统的默认设置去解释执行。
3. Shell 脚本兼容性的核心逻辑
3.1 脚本的第一行决定了解释器
Shell 脚本第一行通常是“shebang”(井号+感叹号),它告诉操作系统用什么解释器来执行这个脚本。
#!/bin/sh#!/bin/bash#!/usr/bin/env bash三种写法的区别:
| 写法 | 含义 | 适用场景 |
|---|---|---|
#!/bin/sh | 使用系统默认的 POSIX Shell | 需要最大兼容性时 |
#!/bin/bash | 使用 Bash 解释器 | 脚本依赖 Bash 特性时 |
#!/usr/bin/env bash | 通过PATH查找 Bash | 路径不固定、虚拟环境场景 |
在实际项目中,如果想写一份在 Linux 和 macOS 上都稳定运行的脚本,优先使用#!/bin/sh或#!/usr/bin/env bash,并尽量只写 POSIX 兼容的语法。这一点是做跨平台脚本最核心的认知。
3.2 POSIX Shell 语法与 Bash 扩展语法的边界
Bash 是“兼容 POSIX 的超集”,它支持 POSIX 语法,同时又增加了许多扩展特性。常见的 Bash 扩展包括:
- 数组
[[ ... ]]条件表达式source/function关键字+=字符串拼接- 进程替换
<(...)、>(...)
这些扩展在 Bash 里非常好用,但如果换到dash或者严格的 POSIXsh环境,就会报错。
下面是一个典型的例子。
#!/bin/bash # Bash 数组写法 fruits=("apple" "banana" "cherry") for fruit in "${fruits[@]}"; do echo "$fruit" done这段脚本在 Bash 里能正常运行,但如果你把第一行改成#!/bin/sh并放在 Ubuntu 的 dash 环境下执行,就会报错:
dash: 2: fruits: not found原因就是 dash 不支持数组语法。因此,如果你的脚本需要跨系统通用,第一件事就是判断自己是否真的需要非 POSIX 特性。
3.3 一个初版可移植脚本的样子
下面是一个符合 POSIX 风格的基础脚本,它不依赖 Bash 扩展,理论上可以在 Linux 和 macOS 的各种 Shell 中运行。
#!/bin/sh echo "Hello, POSIX" echo "当前目录: $(pwd)" for file in *.txt; do [ -f "$file" ] || continue echo "找到文本文件: $file" done这里的要点:
- 使用
$(pwd)而不是`pwd`,后者可读性差。 - 使用
[ -f "$file" ]做文件判断,这是 POSIX 条件表达式。 - 使用
for file in *.txt,通配符展开属于 POSIX 基础行为。
4. 完整实战案例:写一个跨平台系统信息脚本
为了把上面的理论落到实际场景,我们写一个同时适用于 Linux 和 macOS 的系统信息收集脚本。这个脚本会读取系统类型、内核版本、CPU 架构、内存信息、磁盘占用等,并输出成可读的格式。
4.1 创建项目结构
mkdir -p ~/posix-demo cd ~/posix-demo4.2 编写脚本主体
创建文件sysinfo.sh,写入以下内容。
#!/bin/sh # sysinfo.sh - 跨平台系统信息收集脚本 # 适用于 Linux / macOS,遵循 POSIX 基本语法 echo "========================================" echo " 系统信息收集工具" echo "========================================" # 系统名称与内核版本 echo "[系统]" echo "系统类型: $(uname -s)" echo "内核版本: $(uname -r)" echo "CPU 架构: $(uname -m)" echo "主机名: $(uname -n)" echo "" echo "[系统发布信息]" # macOS 使用 sw_vers,Linux 使用 /etc/os-release if [ -f /etc/os-release ]; then . /etc/os-release echo "发行版: $NAME" echo "版本号: $VERSION" elif command -v sw_vers >/dev/null 2>&1; then echo "macOS 版本: $(sw_vers -productVersion)" echo "macOS 构建: $(sw_vers -buildVersion)" else echo "无法识别系统发行信息" fi echo "" echo "[内存信息]" # 不同系统读取内存信息的方式不同 case "$(uname -s)" in Linux) # 从 /proc/meminfo 提取总内存 total_mem=$(awk '/MemTotal/ {print $2 " " $3}' /proc/meminfo) echo "总内存: $total_mem" ;; Darwin) # macOS 通过 sysctl 获取内存 total_mem=$(sysctl -n hw.memsize) total_mem_mb=$((total_mem / 1024 / 1024)) echo "总内存: ${total_mem_mb} MB" ;; *) echo "暂不支持该系统" ;; esac echo "" echo "[磁盘使用情况]" df -h / | awk 'NR==1 || NR==2 {print}' echo "" echo "[当前用户信息]" echo "当前用户: $(id -un)" echo "用户 ID: $(id -u)" echo "组 ID: $(id -g)" echo "Home 目录: $HOME" echo "" echo "[Shell 环境]" echo "当前 SHELL: $SHELL" echo "PATH: $PATH" echo "" echo "========================================" echo " 脚本执行完成" echo "========================================"脚本说明:
uname -s返回系统内核名称,Linux 返回Linux,macOS 返回Darwin。/etc/os-release是绝大多数现代 Linux 发行版都会提供的系统信息文件。sw_vers是 macOS 上读取系统版本专用工具,Linux 上不存在。df -h /用于查看根文件系统占用,两个系统都支持,但格式化输出可能有差异。command -v是 POSIX 标准推荐的“检查命令是否存在”的方式,比which更可移植。
4.3 添加执行权限并运行
chmod +x sysinfo.sh ./sysinfo.sh如果出现Permission denied,说明你没有可执行权限,用上面的chmod加上即可。当然也可以直接用sh sysinfo.sh来执行,不依赖可执行位。
4.4 在 Linux 上运行的效果
在 Ubuntu 22.04 上,输出类似:
======================================== 系统信息收集工具 ======================================== [系统] 系统类型: Linux 内核版本: 5.15.0-XX-generic CPU 架构: x86_64 主机名: ubuntu-server [系统发布信息] 发行版: Ubuntu 版本号: 22.04.3 LTS (Jammy Jellyfish) [内存信息] 总内存: 7951360 kB [磁盘使用情况] Filesystem Size Used Avail Use% Mounted on /dev/vda1 40G 12G 26G 32% / [当前用户信息] 当前用户: root 用户 ID: 0 组 ID: 0 Home 目录: /root [Shell 环境] 当前 SHELL: /bin/bash PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin4.5 在 macOS 上运行的效果
把同样的脚本复制到 macOS 终端执行,输出类似:
======================================== 系统信息收集工具 ======================================== [系统] 系统类型: Darwin 内核版本: 22.6.0 CPU 架构: arm64 主机名: MacBook-Pro.local [系统发布信息] macOS 版本: 13.6 macOS 构建: 22G120 [内存信息] 总内存: 16384 MB [磁盘使用情况] Filesystem Size Used Avail Capacity iused ifree %iused Mounted on /dev/disk3s1s1 232Gi 45Gi 186Gi 20% 394439 48817435 1% / [当前用户信息] 当前用户: user 用户 ID: 501 组 ID: 20 Home 目录: /Users/user [Shell 环境] 当前 SHELL: /bin/zsh PATH: /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin从这里可以看到,同一份脚本,在两种系统上都能正常输出,尽管部分底层命令的实现有差异,但通过条件判断和uname的区分,我们做到了逻辑统一。这就是“面向 POSIX 编写”的价值。
4.6 脚本执行流程小结
这个案例用到了三个关键手段:
- 通过
uname -s判断系统类型。 - 通过
case分支处理 Linux 和 macOS 各自的命令差异。 - 通过
command -v检测命令是否存在,避免调用不存在的工具。
这套模式在真实项目中非常常见,尤其是部署脚本、环境初始化脚本、CI/CD 辅助脚本,几乎都能复用。
5. 常见跨平台差异与高频坑点
即使理解了 POSIX,实际开发中还会遇到不少“看起来一样、用起来不一样”的差异。下面整理几个高频问题。
5.1 sed 命令的差异
这是最经典的一个坑。GNU sed 和 BSD sed 的参数行为不一致。
例如“原地替换”参数:
# GNU sed 支持(Linux) sed -i 's/old/new/g' file.txt # BSD sed 支持(macOS),要求带后缀 sed -i '' 's/old/new/g' file.txt在 macOS 上直接执行sed -i 's/old/new/g' file.txt,会报错invalid option -- i,或者把s/old/new/g当作要备份的后缀名,行为很迷惑。
安全的写法是分开判断,或者尽量不使用-i,改用临时文件方式:
#!/bin/sh tmp_file="${file}.tmp" sed 's/old/new/g' "$file" > "$tmp_file" mv "$tmp_file" "$file"这样在 Linux 和 macOS 上行为一致,只是效率略低,但可移植性更好。
5.2 grep 命令的差异
基础用法两个系统都支持,但扩展正则的写法不一样:
# GNU grep 支持 \d、\+、\? 等转义写法 grep -P '\d+' file.txt # macOS 的 BSD grep 没有 -P 参数如果希望在两个系统都使用扩展正则,建议使用grep -E,并只用 POSIX 兼容的正则语法。
# 匹配数字的 POSIX 写法 grep -E '[0-9]+' file.txt5.3 date 命令的格式化差异
获取“昨天的日期”就是一个例子:
# Linux(GNU date) date -d 'yesterday' +%Y-%m-%d # macOS(BSD date) date -v -1d +%Y-%m-%d两种写法完全不一样。要兼容,可以写一个分支:
#!/bin/sh if date -d 'yesterday' >/dev/null 2>&1; then yesterday=$(date -d 'yesterday' +%Y-%m-%d) else yesterday=$(date -v -1d +%Y-%m-%d) fi echo "昨天: $yesterday"5.4 常用命令差异速查表
| 功能 | Linux(GNU 工具) | macOS(BSD 工具) | 兼容建议 |
|---|---|---|---|
| 获取系统类型 | uname -s | uname -s | 完全一致 |
| 查看发行版 | cat /etc/os-release | sw_vers | 分支处理 |
| sed 原地替换 | sed -i 's/a/b/g' f | sed -i '' 's/a/b/g' f | 使用临时文件 |
| grep 数字 | grep -P '\d+' | 不支持-P | 使用grep -E '[0-9]' |
| date 昨天 | date -d yesterday | date -v -1d | 分支处理 |
| 查看内存 | free -h | vm_stat | 分支处理 |
| 统计文件行数 | wc -l | wc -l | 输出带路径差异 |
| 获取本机 IP | hostname -I | ipconfig getifaddr en0 | 分支处理 |
| 文件校验 | md5sum file | md5 file | 分支处理 |
| 时间统计 | /usr/bin/time | /usr/bin/time | 输出格式差异,需手动解析 |
5.5 常见报错速查
| 报错现象 | 常见原因 | 解决思路 |
|---|---|---|
bad interpreter: /bin/bash^M | 脚本是 Windows 换行符 (CRLF) | 用sed -i 's/\r$//' script.sh转成 LF |
syntax error: unexpected end of file | 脚本中引号未闭合,或使用了 Bash 专有语法但用了#!/bin/sh | 检查引号配对,或改用#!/bin/bash |
command not found: foo | 目标系统没安装该命令 | 用command -v检查,并给出友好提示 |
invalid option -- i | macOS 的 sed 不支持-i的 GNU 写法 | 按上文兼容方案处理 |
Permission denied | 脚本没有可执行权限 | 执行chmod +x script.sh |
xxx: Is a directory | for遍历时未过滤目录 | 在循环内加[ -f "$item" ]判断 |
6. 其他需要注意的工程细节
6.1 如何查看脚本的字符编码
跨平台脚本容易出现中文乱码或bad interpreter,很多情况下是编码和换行符在作祟。
在 Linux 和 macOS 上,可以用file命令快速判断脚本的编码和换行方式:
# 查看脚本文件类型与编码 file sysinfo.sh # 查看脚本中是否包含 CRLF 换行符 file script.sh | grep -i CRLF如果输出里带有CRLF line terminators,说明这个文件用了 Windows 换行。可以统一转成 Unix 换行:
sed -i 's/\r$//' script.sh另外,找到一段有问题的内容时,可以用od查看原始字节:
od -c script.sh | head -20这会输出每个字符的 ASCII 码,帮助定位不可见字符。
6.2 不同 Shell 之间的兼容矩阵
很多新手分不清sh、bash、zsh、dash之间的关系。这里画一个简单的层次关系。
POSIX 规范 └── sh(标准解释器) ├── bash(兼容 POSIX + 扩展) ├── dash(轻量级 POSIX 解释器) └── zsh(兼容 POSIX + 更多扩展)- Ubuntu 的
/bin/sh默认是 dash,执行速度快,但只支持 POSIX 语法。 - CentOS 的
/bin/sh默认是 bash,但对sh调用时会进入 POSIX 模式。 - macOS 的
/bin/sh是 bash 的 POSIX 兼容模式。
所以,最稳妥的跨平台策略是:脚本第一行写#!/bin/sh,然后用 POSIX 语法写。如果实在离不开 Bash 扩展,就把第一行写成#!/bin/bash,并接受“某些纯 POSIX 环境下不可用”的代价。
6.3 环境变量和 PATH 差异
Linux 和 macOS 在环境变量上也存在差异。PATH是最典型的例子。
macOS 默认的PATH可能不包含/usr/local/sbin、/opt/homebrew/bin(Apple Silicon 默认 Homebrew 前缀),导致你在 Linux 上能执行的命令,在 macOS 上找不着。
处理方式是在脚本开头集中补充环境变量:
#!/bin/sh # 如果目录存在则加入 PATH if [ -d "/opt/homebrew/bin" ]; then PATH="/opt/homebrew/bin:$PATH" fi if [ -d "/usr/local/bin" ]; then PATH="/usr/local/bin:$PATH" fi export PATH6.4 冷门但实用的 POSIX 特性
除了基本语法,POSIX 还定义了一些容易被忽略的特性,它们在跨平台脚本中非常有用。
IFS(内部字段分隔符)控制for循环和read的字段分割方式。默认情况下包含空格、制表符和换行符。如果文件名里带空格,建议显式处理:
#!/bin/sh # 遍历文件名带空格的文件时,利用通配符和 set -- 技巧 for file in *.txt; do [ -e "$file" ] || continue echo "处理文件: $file" donetrap用于捕获信号,可以保证脚本被中断时也能清理临时文件:
#!/bin/sh tmp_file=$(mktemp) trap 'rm -f "$tmp_file"' EXIT INT TERM echo "临时文件路径: $tmp_file"这个脚本在 Linux 和 macOS 上都能运行,trap和mktemp都是 POSIX 兼容的。
6.5 部署脚本中的安全注意事项
跨平台部署脚本经常要处理权限、路径、临时文件等问题。这里提供几个实用建议:
- 所有变量在引用时加双引号,例如
"$file",避免路径带空格导致意外。 - 避免使用
rm -rf拼接变量时没有加路径判断。至少加一个前置检查:case "$target_dir" in /tmp/*|$HOME/*) ;; *) echo "拒绝删除非指定目录"; exit 1 ;; esac - 使用
mktemp创建临时文件,不要用固定的/tmp/xxx,避免多用户冲突。 - 执行可能影响系统配置的操作时,在脚本里打印足够清晰的日志,方便事后回溯。
- 生产环境运行前,先在测试机验证脚本内容和权限无误。
7. 如何检查自己的脚本是否真的可移植
写完脚本后,光靠眼睛看是不够的。可以使用下面几种方式提高脚本的可移植性。
7.1 使用 shellcheck 做静态检查
ShellCheck 是一个很经典的 Shell 脚本静态分析工具,它能找出语法错误、潜在 Bug、以及非 POSIX 语法。
# 安装(Linux) sudo apt install shellcheck # 安装(macOS) brew install shellcheck # 检查脚本 shellcheck sysinfo.sh如果一个脚本开头是#!/bin/sh,ShellCheck 会严格按照 POSIX 规则来检查,并提示哪些语法是 Bash 扩展。
7.2 使用 dash 做严格测试
在 Ubuntu 上,可以直接用 dash 执行脚本,模拟严格的 POSIX 环境:
dash script.sh如果脚本在 dash 下能正常执行,说明它作为 POSIX 脚本的兼容性很好。
7.3 多系统测试清单
建议在发布脚本前,至少在这一组环境里过一遍:
- Ubuntu + bash
- Ubuntu + dash
- macOS + zsh
- macOS + bash(通过
bash script.sh调用) - macOS + sh
如果脚本在以上所有环境都输出一致,那么它的跨平台能力基本合格。
8. 最佳实践与工程建议
8.1 命名与结构规范
- 脚本文件名统一使用小写字母、下划线分隔,例如
backup_db.sh。 - 文件内部分区清晰:头部注释、变量定义、函数定义、主逻辑。
- 函数命名使用动词开头,例如
check_env、parse_args、do_backup。 - 每个函数只做一件事,控制单函数体量。
8.2 参数解析与异常处理
优先使用getopt或手动循环解析,不推荐直接依赖$1、$2的固定位置参数,因为功能一多就混乱。
手动解析示例:
#!/bin/sh usage() { echo "用法: $0 [-h] [-n name]" exit 1 } while getopts "hn:" opt; do case "$opt" in h) usage ;; n) name="$OPTARG" ;; *) usage ;; esac done echo "姓名: ${name:-未提供}"这里的getopts是 POSIX 内置命令,Linux 和 macOS 都支持。
8.3 日志与可观测性
跨平台脚本跑在服务器上,没有人工盯着,所以日志很重要。
建议遵循以下原则:
- 统一日志时间格式,如
2025-04-01 12:00:00 [INFO] msg。 - 用
set -u防止未定义变量,用set -e在出错时快速终止。但要注意,set -e在某些条件下反而会掩盖错误,建议谨慎使用。 - 保存退出码:调用命令后立即记录
$?,避免中间被其他命令覆盖。 - 对关键操作打点,例如开始备份、备份结束、备份失败。
8.4 生产环境提醒
- 不要在未备份的情况下执行批量删除或更新命令。
- 脚本中涉及
sudo时,要提示用户,并检查当前用户权限。 - 涉及数据库操作时,先备份,再执行,并保留回滚思路。
- 对敏感信息使用环境变量或密钥管理工具,不硬编码在脚本里。
- 涉及系统级配置修改时,优先输出 diff,再写入文件。
8.5 维护成本控制
没有注释的脚本三个月后连自己都看不懂。建议在脚本头部写明:
# 脚本名: sysinfo.sh # 作者: xxx # 日期: 2025-04-01 # 说明: 收集系统基础信息,支持 Linux / macOS # 用法: ./sysinfo.sh9. 总结与下一步学习建议
通过本文,我们可以掌握几个关键点:
- POSIX 是一套操作系统接口标准,不是某个具体系统。
- Linux 和 macOS 都在不同程度上兼容 POSIX,这是两者 Shell 脚本能够通用的基础。
- 跨平台脚本的核心策略是:写 POSIX 兼容语法、用
uname区分系统、用case分支处理差异。 - 常见跨平台差异集中在
sed、grep、date等基础命令上,不是脚本逻辑本身的问题。 - 工程上要养成检查编码、换行符、PATH、临时文件、日志规范的习惯。
如果你目前写的脚本主要在 Linux 上跑,也不建议直接跳过 POSIX。因为现在很多环境是混合的:开发机用 macOS,CI 构建机用 Linux,服务器是 CentOS 或 Ubuntu。掌握了可移植脚本的思路,后续维护成本会明显降低。
接下来可以继续深入的方向:
- 学完
sh语法后,再学awk和sed的进阶用法,它们是文本处理的主力。 - 了解 Systemd 服务单元或 launchd 的配置方式,让脚本能开机自启。
- 学习 CI/CD 流程中如何嵌入 Shell 脚本,例如 GitLab CI、GitHub Actions。
- 在开源项目里多读别人的脚本,观察他们是如何处理兼容性和异常分支的。
最后留一个思考题:如果你拿到一份没有 shebang 的脚本文件,在 Linux 上执行./script.sh会怎么样?在 macOS 上又怎么样?建议你亲手试一下,这能帮助你更直观地理解“脚本解释器由谁决定”这件事。动手实验永远比看文章印象更深。