跳到主要内容

POSIX 标准与 Shell 详解

POSIX 是什么

POSIX(Portable Operating System Interface)是由 IEEE(电气和电子工程师协会)制定的一系列标准,正式编号为 IEEE Std 1003,目标是让软件在不同的类 Unix 系统之间可移植。

名字里的 "X" 是 Unix 的意思,由 Richard Stallman 建议加上。可以把它理解为一份合同:实现了 POSIX 的系统,遵循标准编写的程序就能跨平台运行。


历史背景

1969 Unix 诞生(Bell Labs,Ken Thompson + Dennis Ritchie)

1970s 各公司开始基于 Unix 派生自己的系统
├─ BSD(加州大学伯克利分校)
├─ System V(AT&T)
├─ AIX(IBM)
├─ HP-UX(HP)
└─ SunOS(Sun)

问题:各家实现不兼容,同一个程序在不同系统上跑不起来

1988 IEEE 发布 POSIX.1,统一接口标准
1997 Single UNIX Specification (SUS) 与 POSIX 合并
2001 POSIX:2001 / SUSv3(现代 POSIX 的基础)
2008 POSIX:2008(当前主流版本,又叫 SUSv4)

POSIX 标准覆盖的内容

POSIX 不只是 shell 标准,它定义了整个操作系统的接口层:

1. C 语言 API(系统调用接口)

fork() // 创建子进程
exec() // 执行程序
open() // 打开文件
read() // 读文件
write() // 写文件
pipe() // 创建管道
signal() // 信号处理
pthread_*() // 线程相关

2. Shell 语言规范

# 条件判断
if [ -f file ]; then
echo "exists"
fi

# 循环
for i in 1 2 3; do
echo $i
done

# 函数定义
func() {
return 0
}

3. 命令行工具规范

POSIX 规定了这些工具必须存在且行为一致:

类别工具
文件操作ls cp mv rm mkdir find
文本处理grep sed awk sort cut tr
进程管理ps kill wait
压缩归档tar cpio
编译开发make cc ar
Shellsh echo test expr

4. 文件系统结构

/ 根目录
/bin 基本命令
/etc 配置文件
/tmp 临时文件
/usr 用户程序
/var 可变数据

5. 正则表达式规范

POSIX 定义了两种正则:

# BRE(Basic Regular Expression)
grep "a\{3\}" file # 匹配 aaa

# ERE(Extended Regular Expression)
grep -E "a{3}" file # 匹配 aaa

POSIX Shell 具体规定了什么

变量展开

${var} # 基本展开
${var:-default} # 未定义时用默认值
${var:=default} # 未定义时赋值并展开
${var:?error} # 未定义时报错退出
${var:+other} # 已定义时展开 other
${#var} # 变量长度
${var%pattern} # 从右删除最短匹配
${var%%pattern} # 从右删除最长匹配
${var#pattern} # 从左删除最短匹配
${var##pattern} # 从左删除最长匹配

POSIX 没有的(bash/zsh 扩展)

${var/old/new} # 字符串替换 ← bash 扩展
${var^^} # 转大写 ← bash 扩展
declare -A map # 关联数组 ← bash 扩展
[[ ]] # 增强条件 ← bash 扩展
(( )) # 算术命令 ← bash 扩展(POSIX 只有 $(( )))
shopt # shell 选项 ← bash 扩展

特殊内建命令(Special Built-in Utilities)

POSIX 将以下命令列为特殊内建,与普通命令有三点核心差异:

break : . continue eval exec exit
export readonly return set shift times trap unset

差异一:命令前的变量赋值会持久化

# 普通命令:var 只在该命令作用域内有效
var=hello echo "$var" # 空

# 特殊内建:var 持久保留在当前 shell
var=hello export PATH
echo "$var" # hello

差异二:错误会导致 shell 退出(POSIX 模式下)

set -o posix

readonly x=1
x=2 # 错误:赋值只读变量,shell 立即退出

cd /nonexistent # 普通内建失败,shell 继续执行
echo "还在运行" # 会执行

差异三:函数无法覆盖特殊内建(POSIX 模式下)

# bash 默认模式:函数可以覆盖
exit() { echo "拦截了 exit"; }
exit # 输出"拦截了 exit",不真正退出

# POSIX 模式:特殊内建优先,函数覆盖无效
set -o posix
exit() { echo "拦截了 exit"; }
exit # 直接退出,函数被忽略

各 Shell 与 POSIX 的关系

演化谱系

Thompson sh(1971,Unix 原始 shell)

├─→ Bourne sh(1979,/bin/sh 的起源)
│ │
│ ├─→ POSIX sh 标准(1988,以 Bourne sh 为基础制定)
│ │ ├─→ dash(严格 POSIX,轻量快速,Ubuntu 的 /bin/sh)
│ │ └─→ sh(各系统的 /bin/sh)
│ │
│ ├─→ ksh(Korn Shell,1983,贡献了大量特性给 POSIX)
│ │
│ └─→ bash(1989,GNU,POSIX 兼容 + 大量扩展)
│ └─→ zsh(1990,兼容 bash/ksh + 更多扩展)

└─→ csh(1978,独立分支,不兼容 POSIX)
└─→ tcsh(csh 改进版)

各 Shell 对 POSIX 的支持程度

ShellPOSIX 支持说明
dash严格遵守Ubuntu 的 /bin/sh,几乎无扩展,速度最快
sh直接实现通常是 dash 或 bash 的软链接
bash兼容并扩展默认有扩展,set -o posix 切换严格模式
zsh基本兼容默认行为有差异,emulate sh 切换兼容模式
ksh高度兼容POSIX 标准的重要参考来源
csh / tcsh不兼容独立分支,语法完全不同

csh 是个特例

csh 语法仿照 C 语言,与 POSIX shell 完全不兼容:

set name = "world"
if ($name == "world") then
echo "hello $name"
endif

foreach i (1 2 3)
echo $i
end

业界著名文章 "Csh Programming Considered Harmful" 明确建议不要用 csh 写脚本。tcsh 是 csh 的改进版,主要用作交互式 shell。


bash 与 zsh 的扩展对比

同样的功能,POSIX 写法 vs 各 Shell 扩展写法:

# ── 递归查找文件 ──────────────────────────────
# POSIX(sh/dash/bash/zsh 都能用)
for f in $(find . -name "*.sh"); do
echo "$f"
done

# bash 扩展
shopt -s globstar
for f in **/*.sh; do
echo "$f"
done

# ── 关联数组 ──────────────────────────────────
# POSIX:不支持

# bash 扩展
declare -A map
map[key]="value"
echo ${map[key]}

# ── 字符串替换 ────────────────────────────────
# POSIX
echo "$var" | sed 's/old/new/'

# bash 扩展
echo "${var/old/new}"

# ── 条件判断 ──────────────────────────────────
# POSIX
if [ "$a" = "$b" ] && [ -f "$file" ]; then ...

# bash 扩展
if [[ "$a" == "$b" && -f "$file" ]]; then ...

符合 POSIX 的系统

系统说明
macOS通过官方 POSIX 认证(基于 BSD)
AIXIBM,通过认证
HP-UXHP,通过认证
SolarisOracle,通过认证
Linux未通过官方认证,但行为高度兼容
Windows不符合,WSL 是变通方案

Linux 没有通过官方 POSIX 认证的原因是认证费用昂贵,而非技术上不兼容。


命令查找顺序

模式查找顺序
bash 默认别名 → 函数 → 内建(含特殊内建)→ 外部命令
POSIX 模式别名 → 特殊内建 → 函数 → 普通内建 → 外部命令

实际使用建议

场景推荐原因
需要跨平台移植的脚本#!/bin/sh只用 POSIX 语法,dash/bash/zsh 均可运行
只在 Linux/Mac 运行#!/bin/bash可用 bash 扩展,功能更强
交互式终端zsh / bash扩展特性让日常操作更便捷
系统启动脚本#!/bin/sh(dash)速度快,无额外依赖
检查脚本可移植性shellcheck自动检测非 POSIX 用法

检查脚本是否符合 POSIX

# 安装 shellcheck
brew install shellcheck # macOS
apt-get install shellcheck # Ubuntu

# 检查脚本
shellcheck -s sh script.sh # 按 POSIX sh 标准检查
shellcheck -s bash script.sh # 按 bash 标准检查

# bash 中临时切换 POSIX 模式测试
set -o posix
# ... 执行脚本 ...
set +o posix

判断当前 shell 是否为 POSIX 模式

# bash 中检查
[[ $POSIXLY_CORRECT ]] && echo "POSIX 模式" || echo "bash 扩展模式"

# zsh 中切换兼容模式
emulate sh # 模拟 POSIX sh
emulate zsh # 恢复 zsh 模式
emulate bash # 模拟 bash

一句话总结

POSIX 是 Unix 世界的"语言标准"——就像 ECMAScript 之于 JavaScript,POSIX 定义了 Unix 系统必须提供什么接口。Shell 只是 POSIX 标准的一部分,它同时还规定了 C API、命令行工具、文件系统结构等整个操作系统层面的接口。各 Shell(bash、zsh、ksh)在兼容 POSIX 的基础上各自扩展,csh 是唯一走了完全不同路线的主流 shell。