模块:S13 可移植性与规范
篇号:S13-01 / 42
预计阅读:50 分钟
主线:Bash(对照 POSIXsh)
文章目录
- 本篇目标
- 30 秒速览
- 正文
- 1. 先看清 shebang
- 2. POSIX sh 与 Bash 关系(够用理解)
- 3. 总对照表(语法)
- 4. 条件表达式(最常踩坑)
- 4.1 `[` vs `[[`
- 4.2 整数比较
- 4.3 组合条件
- 5. 参数展开
- 6. 循环与控制
- 7. 函数与作用域
- 8. `set` 选项
- 9. 读脚本:红旗列表
- 10. 何时选 sh、何时选 Bash
- 11. 从 Bash 迁到 sh 的替换思路(预览)
- 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 语法?
#!/bin/shif[[-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 | 不可移植 | 常用 | 用printfS13-02 |
read -r | ✅ | ✅ | S07-02 |
trapEXIT | 部分 | ✅ | S11-01 |
flock | 非 POSIX 命令 | 常见 | S11-02 |
timeout | 非 POSIX 命令 | GNU | S11-02 |
4. 条件表达式(最常踩坑)
4.1[vs[[
# POSIXif["$a"="$b"];thenif[-f"$file"];then# Bash onlyif[["$a"=="$b"]];thenif[["$f"==*.log]];then4.2 整数比较
# POSIX["$n"-eq10]# Bash(脚本内常用,但 sh 不行)((n>=10))4.3 组合条件
# 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. 循环与控制
# POSIXforfin"$@";dowhile["$n"-lt10];don=$((n+1))done# Bash onlyforiin{1..10};dofor((i=0;i<10;i++));dobreak/continue:POSIX 均有。
7. 函数与作用域
# POSIX 函数myfunc(){echo"$1"}# Bash 扩展myfunc(){localx=1declare-in=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不可用(ShellCheckSC3043)。
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
shellcheck-sshscript.sh# 按 POSIX 扫shellcheck-sbashscript.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 专有写法:
#!/bin/shset-euopipefailfiles=("$@")[[${#files[@]}-lt1]]&&exit2source./lib.shforiin{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 模块。