模块S13 可移植性与规范篇号S13-02 / 42预计阅读45 分钟主线POSIXsh与 Bash 对照实践文章目录本篇目标30 秒速览正文1. 先定策略sh 还是 Bash2. 可移植脚本骨架3. command -v检查依赖4. command绕过别名与函数5. printf 替代 echo6. 加载与路径7. 条件、循环、模式8. set -e、-u、pipefail9. IFS 与 read10. cd、mktemp、外部命令11. 可移植性检查清单可打印12. Bash 脚本不必强行 sh 化13. 读脚本从 POSIX 视角扫一眼练习判断题改错题实操题S13 模块小结下一篇预告本篇目标在S13-01 对照表基础上掌握可移植 sh 脚本的编写清单command -v、command避别名、printf替代echo -e、.代替source、安全的IFS/read/cd。能判断「这篇应坚持 POSIX」还是「应改 shebang 为 Bash」并写出通过dash或shellcheck -s sh检查的脚本骨架。30 秒速览明确目标要POSIX sh就#!/bin/sh 禁 Bash 语法要 Bash 就#!/usr/bin/env bash别混写。command -v cmd检测依赖是否存在比which更可移植。command cmd绕过函数/别名执行真正的cmd。printf格式化输出避免echo -e、echo选项实现相关。.加载库[/test条件case做模式算术用$(( ))。验证shellcheck -s sh、在dash上跑一遍。正文1. 先定策略sh 还是 Bash选择做法Bash 专用专栏多数篇#!/usr/bin/env bash可用[[、数组、pipefailPOSIX sh#!/bin/sh遵守本篇清单误标 sh最常见 bugshebang 写 sh 内容写 Bash读脚本作者写#!/bin/sh时按POSIX理解若满篇 Bash 语法按S13-01 红旗处理。2. 可移植脚本骨架#!/bin/shset-eu# 注意POSIX sh 未必有 pipefail、-u 行为一致见 §8# 依赖command-vawk/dev/null21||{printfneed awk\n2exit1}# 用法if[$#-lt1];thenprintfusage: %s file\n$02exit2fimain(){file$1if[!-f$file];thenprintfnot found: %s\n$file2return1fi...}main$与S01-04 Bash 骨架对比无pipefail或确认实现支持再开、函数return与exit分清。3.command -v检查依赖need_cmd(){command-v$1/dev/null21||{printfrequired command not found: %s\n$12exit1}}need_cmdawkneed_cmdsed对比说明command -vPOSIX查PATH里可执行名which非标准、可能输出别名不推荐type交互有用脚本里优先command -v不要用if which awk写门禁S11-02 与本篇统一。4.command绕过别名与函数# 用户定义了 ls 别名时仍要真实 lscommandls-la# 避免无限递归mygrep(){commandgrep$;}读脚本command前缀表示「我要外部命令不是 shell 函数」。5.printf替代echoecho的问题-e、-n非 POSIX各系统行为不一部分echo -n、echo \c实现不同变量含-n开头可能被当选项历史坑# 不好可移植差echo-eline1\nline2echo$maybe_option_like# 好printf%s\nline1printf%s\nline2printfusage: %s file\n$02printfvalue%s\n$var需求printf一行 换行printf %s\n $x无换行printf %s $x多参数多行printf %s\n $a $b格式数字printf n%d\n $nn应为整数不要在可移植脚本里依赖echo -eS13-01。6. 加载与路径# POSIX../lib/common.sh# 非 POSIXBash# source ./lib/common.sh路径# 避免依赖调用时的 cwdSCRIPT_DIR$(CDPATHcd--$(dirname$0)pwd).$SCRIPT_DIR/lib/common.shCDPATH防止 cd 被干扰POSIX 常见写法。7. 条件、循环、模式# 条件if[-f$f][-r$f];then...fi# 参数个数if[$#-lt1];thenexit2fi# 模式 → case代替 [[ *.log ]]case$filein*.log)process_log$file;;*)printfskip: %s\n$file2;;esac# 循环forfin$;do[-f$f]||continue...done# 算术i0while[$i-lt10];doi$((i1))done8.set -e、-u、pipefail选项POSIX sh 建议set -e可用注意if、**set -u多数dash支持极老 sh 慎用pipefail非 POSIXsh 脚本不要写#!/bin/shset-eu# 管道失败拆开检查或不用管道决策greppatternfile/dev/null21||exit1Bash 脚本继续set -euo pipefail专栏默认。9.IFS与readIFSread-rline# 或whileIFSread-rline;do...donefile-rPOSIX不处理反斜杠临时改IFS后恢复old_ifs$IFSIFS:# ... 使用IFS$old_ifs10.cd、mktemp、外部命令cd$dir||exit1tmp$(mktemp)||exit1traprm -f $tmpEXIT INT HUPTERMmktempGNU/BSD 选项不同严格 sh 环境可用tmp$$.$RANDOM较弱或要求用户指定路径——读脚本看平台假设。GNU 专用选项sed -i、grep --include在 sh 脚本里要检测或文档说明S10、S11。11. 可移植性检查清单可打印文件头shebang 与目标一致sh或bash无[[(( ))数组{1..10}source( )sh 时依赖与命令command -v检查必备命令外部命令考虑command前缀无 Bash 专有内置假设输出与数据用printf不用echo -e变量$var、$IFS read -r读行控制与错误[/testcasecd ... || exitsh 无pipefail时管道显式查$?验证shellcheck -s sh或-s bash在dash/ 目标环境跑回归12. Bash 脚本不必强行 sh 化团队统一Bash、CI 镜像有 Bash、需要数组/[[/pipefail时#!/usr/bin/env bashset-euopipefail可移植不是越高越好与声明一致最重要S14-01 风格约定。13. 读脚本从 POSIX 视角扫一眼第一行sh还是bash输出用printf还是echo -e依赖which还是command -v模式用case还是[[ 管道在无 pipefail时是否误判成功练习判断题command -v比which更适合写进 POSIX 脚本。printf %s\n $x比echo -e更可移植。POSIX sh 脚本应使用source加载库。set -o pipefail应写在所有#!/bin/sh脚本里。声称可移植的脚本应在dash或shellcheck -s sh下验证。参考答案对。对。错用.。错非 POSIXdash 不支持。对。改错题把下面改成POSIX sh 风格保持逻辑#!/bin/shset-euopipefail[[-f$1]]||{echomissing;exit1;}source./lib.shecho-eprocessing\n$1参考#!/bin/shset-euif[!-f$1];thenprintfmissing\n2exit1fi../lib.shprintfprocessing\n%s\n$1去掉pipefail或改 bash shebang[[→[source→.printf。实操题写need_cmd函数接受一个命令名不存在则printf错误到 stderr并以1退出。参考答案need_cmd(){if!command-v$1/dev/null21;thenprintfrequired command not found: %s\n$12exit1fi}S13 模块小结篇号主题S13-01语法对照表、shebang 红旗S13-02清单、command、printf、POSIX 骨架下一模块S14讲风格、模板、综合拆脚本含必读三星S14-03。下一篇预告S14-01《风格约定命名、引用、函数与错误处理》— 工程化习惯与可移植性并列。