模块 :S13 可移植性与规范
篇号 :S13-01 / 42
预计阅读 :50 分钟
主线 :Bash(对照 POSIX sh)
文章目录
-
- 本篇目标
- [30 秒速览](#30 秒速览)
- 正文
-
- [1. 先看清 shebang](#1. 先看清 shebang)
- [2. POSIX sh 与 Bash 关系(够用理解)](#2. POSIX sh 与 Bash 关系(够用理解))
- [3. 总对照表(语法)](#3. 总对照表(语法))
- [4. 条件表达式(最常踩坑)](#4. 条件表达式(最常踩坑))
-
- [4.1 `[` vs `[[`](#4.1
[vs[[) - [4.2 整数比较](#4.2 整数比较)
- [4.3 组合条件](#4.3 组合条件)
- [4.1 `[` vs `[[`](#4.1
- [5. 参数展开](#5. 参数展开)
- [6. 循环与控制](#6. 循环与控制)
- [7. 函数与作用域](#7. 函数与作用域)
- [8. `set` 选项](#8.
set选项) - [9. 读脚本:红旗列表](#9. 读脚本:红旗列表)
- [10. 何时选 sh、何时选 Bash](#10. 何时选 sh、何时选 Bash)
- [11. 从 Bash 迁到 sh 的替换思路(预览)](#11. 从 Bash 迁到 sh 的替换思路(预览))
- [12. 与 ShellCheck](#12. 与 ShellCheck)
- 读脚本检查清单
- 练习
- 下一篇预告
本篇目标
建立 POSIX sh 与 Bash 的语法对照 :读到 #!/bin/sh 却满篇 [[、(( )) 时能判断风险;写脚本时能决定坚持 POSIX 还是明确 Bash 。本篇是对照表 ;编写清单与 command/printf 在 S13-02。
30 秒速览
- POSIX sh :各系统上的
/bin/sh(可能是 dash 、bash 的 posix 模式 等),语法最小公共子集。 - Bash :扩展多------
[[、(( ))、数组、{1..10}、pipefail、进程替换等。 - shebang 与内容要一致 :
#!/bin/sh里不要写 Bash 专用语法。 - 可移植 优先:
[、test、$(( ))、printf、.、getopts。 - 本专栏主线是 Bash ;需要 sh 时查本篇 + S13-02。
正文
1. 先看清 shebang
| 第一行 | 含义 |
|---|---|
#!/bin/sh |
用系统 sh 解释(不保证是 Bash) |
#!/usr/bin/env bash |
明确要 Bash(专栏推荐) |
#!/bin/bash |
Bash,路径写死(部分系统无此路径) |
读脚本第一问:第一行承诺的运行时,是否支持下文里的 Bash 语法?
bash
#!/bin/sh
if [[ -f "$f" ]]; then # 错:[[ 不是 POSIX
常见情况:作者本机 /bin/sh 指向 bash ,脚本在 dash / Alpine busybox sh 上直接报错。
2. POSIX sh 与 Bash 关系(够用理解)
- POSIX:标准文档规定 shell 语法子集。
- 实现 :Linux 上
sh常为 dash (Debian/Ubuntu)或 bash --posix;macOS 曾长期不同。 - Bash :超集;
bash script.sh可跑 Bash 语法;sh script.sh只能跑 POSIX(加实现少量扩展)。
专栏策略 :教学用 Bash;交付若要求 sh,按本篇改写法。
3. 总对照表(语法)
| 特性 | POSIX sh | Bash | 专栏 |
|---|---|---|---|
条件 if [ ] |
✅ | ✅ | S05-01 |
[[ ]] |
❌ | ✅ | S05-03 |
正则 =~ |
❌ | ✅ [[ ]] |
S05-03 |
模式 == *.log |
❌ | ✅ [[ ]] |
S05-03 |
算术测试 (( )) |
❌ | ✅ | S05-04 |
[ "$n" -eq 10 ] |
✅ | ✅ | S05-01 |
let |
部分 | ✅ | S05-04 |
function f() |
❌(f() 可以) |
✅ | S08-01 |
local |
❌ | ✅ | S08-02 |
数组 arr=( ) |
❌ | ✅ | S03-04 |
${arr[@]} |
❌ | ✅ | S03-04 |
${var:0:5} 子串 |
❌ | ✅ | S03-02 |
${var:-def} |
部分(POSIX 有子集) | ✅ | S03-02 |
for i in {1..10} |
❌ | ✅ | S07-01 |
for (( )) |
❌ | ✅ | S07-01 |
select |
❌ | ✅ | S08-04 |
getopts |
✅ | ✅ | S08-04 |
source |
❌(用 .) |
✅ 同 . |
S08-03 |
$(cmd) |
✅ | ✅ | S09-03 |
进程替换 <( ) |
❌ | ✅ | S09-04 |
set -o pipefail |
❌ | ✅ | S01-04、S09-02 |
PIPESTATUS |
❌ | ✅ | S09-02 |
&> 重定向 |
❌ | ✅ | S09-01 |
here-string <<< |
❌ | ✅ | S09-01 |
[[ 内 && |
--- | ✅ | S05-03 |
echo -e |
不可移植 | 常用 | 用 printf S13-02 |
read -r |
✅ | ✅ | S07-02 |
trap EXIT |
部分 | ✅ | S11-01 |
flock |
非 POSIX 命令 | 常见 | S11-02 |
timeout |
非 POSIX 命令 | GNU | S11-02 |
4. 条件表达式(最常踩坑)
4.1 [ vs [[
bash
# POSIX
if [ "$a" = "$b" ]; then
if [ -f "$file" ]; then
# Bash only
if [[ "$a" == "$b" ]]; then
if [[ "$f" == *.log ]]; then
4.2 整数比较
bash
# POSIX
[ "$n" -eq 10 ]
# Bash(脚本内常用,但 sh 不行)
(( n >= 10 ))
4.3 组合条件
bash
# POSIX:两个 [ 或 -a(不推荐 -a)
[ "$a" = "x" ] && [ "$b" = "y" ]
# Bash
[[ "$a" == "x" && "$b" == "y" ]]
5. 参数展开
| 写法 | POSIX sh | Bash |
|---|---|---|
$1 "$@" |
✅ | ✅ |
${var:-def} |
✅ | ✅ |
${var:=def} |
✅ | ✅ |
${#var} |
✅ | ✅ |
${var#prefix} |
❌ | ✅ |
${var/pat/rep} |
❌ | ✅ |
数组 "${a[@]}" |
❌ | ✅ |
写 #!/bin/sh 时避免 S03-02 里 Bash 专有展开;S13-02 给替代写法。
6. 循环与控制
bash
# POSIX
for f in "$@"; do
while [ "$n" -lt 10 ]; do
n=$(( n + 1 ))
done
# Bash only
for i in {1..10}; do
for (( i=0; i<10; i++ )); do
break / continue:POSIX 均有。
7. 函数与作用域
bash
# POSIX 函数
myfunc() {
echo "$1"
}
# Bash 扩展
myfunc() {
local x=1
declare -i n=0
}
export -f:Bash,非 POSIX。
8. set 选项
| 选项 | POSIX sh | Bash |
|---|---|---|
set -e |
✅ | ✅ |
set -u |
部分 | ✅ |
set -o pipefail |
❌ | ✅ |
set -x |
✅ | ✅ |
#!/bin/sh + set -euo pipefail :在 dash 上 -u 通常可用 ,pipefail 不可用 (ShellCheck SC3043)。
9. 读脚本:红旗列表
见到 #!/bin/sh (或 dash)却出现:
[[(( )){1..10}source(应用.)- 数组、
${var//x/y} <( )&>select
→ 要么 shebang 错了,要么脚本在部分系统会挂 。应改为 #!/usr/bin/env bash 或改写成 POSIX(S13-02)。
见到 #!/usr/bin/env bash:可合理使用专栏全部 Bash 特性。
10. 何时选 sh、何时选 Bash
| 选 POSIX sh | 选 Bash |
|---|---|
| 嵌入式/极简 rootfs | 开发机、CI、团队统一 Bash |
| 明确要求 dash 兼容 | 需要 数组、[[、pipefail |
| 被 sh 调用的极小片段 | 复杂逻辑、运维工具(专栏默认) |
不要假设「sh 就是 bash」。
11. 从 Bash 迁到 sh 的替换思路(预览)
| Bash | POSIX 倾向 |
|---|---|
[[ -f f ]] |
[ -f f ] |
(( $# < 1 )) |
[ "$#" -lt 1 ] |
[[ $x == *.log ]] |
case $x in *.log) |
${s#pre} |
sed/expr(笨但可移植) |
source f |
. f |
pipefail |
拆开管道或显式查 $? |
细节与 command/printf 在 S13-02。
12. 与 ShellCheck
bash
shellcheck -s sh script.sh # 按 POSIX 扫
shellcheck -s bash script.sh # 按 Bash 扫
shebang 与 -s 一致。见 S12-02。
读脚本检查清单
- 第一行 shebang 与文内语法是否一致?
-
#!/bin/sh下是否误用[[、数组、pipefail? - 需要 模式匹配 时,sh 是否改用
case? - 整数比较是否用
-eq而非(( ))(在 sh 脚本里)? - 是否用
printf代替echo -e(S13-02)?
练习
判断题
#!/bin/sh保证脚本在所有 Unix 上由 Bash 执行。[[是 Bash 扩展,不是 POSIX。getopts可在 POSIX sh 脚本中使用。set -o pipefail在所有/bin/sh实现上都可用。- 读脚本时 shebang 与语法冲突应优先信语法还是信 shebang?------应让它们一致。
参考答案
- 错。
- 对。
- 对。
- 错。
- 应一致;冲突时按更严格的 sh 理解或改 shebang。
改错题
下面脚本声称兼容 sh,请指出 Bash 专有写法:
bash
#!/bin/sh
set -euo pipefail
files=( "$@" )
[[ ${#files[@]} -lt 1 ]] && exit 2
source ./lib.sh
for i in {1..3}; do
[[ "$i" =~ ^[0-9]+$ ]] && run "$i"
done
参考答案
- 数组
files=( )、${#files[@]} [[、=~、模式pipefail(dash sh 不行)source→.{1..10}
应改为#!/usr/bin/env bash或逐项 POSIX 化(S13-02)。
对照题
将下列 Bash 改为 POSIX [ / test 风格(不要求可运行,表达即可):
[[ -f "$f" && -r "$f" ]](( $# >= 1 ))[[ "$x" == *.log ]]
参考答案
[ -f "$f" ] && [ -r "$f" ][ "$#" -ge 1 ]case "$x" in *.log) ... ;; esac或[ -f "$x" ]等按语义改写
下一篇预告
S13-02 :《可移植脚本编写清单与 command/printf》--- 实战 checklist、command -v、避免 echo 陷阱,完成 S13 模块。