Linux命令-sh(Bourne Shell 与 POSIX 模式 Shell)
Linux命令-sh(Bourne Shell 与 POSIX 模式 Shell)
- 🔰 简介
- 📖 语法
- ⚙️ 选项
- 💡 实战示例
- 示例 1:sh -c 字符串执行(最常用)
- 示例 2:sh 调试 - x / v / n
- 示例 3:sh vs bash 关键差异
- 示例 4:脚本可移植性 - shebang 选择
- 示例 5:登录 sh 与交互 sh
- 示例 6:sh 在系统脚本中的角色
- 示例 7:sh -c 的安全与转义
- 示例 8:写真正的 sh 脚本(POSIX 严格)
- ⚠️ 注意事项
- 📝 总结
- 📚 相关命令
快速参考sh通常指代/bin/sh——Linux 上的POSIX 标准 Shell 解释器。在历史上指 Bourne Shell(Stephen Bourne 于 1977 年在贝尔实验室开发),在现代 Linux 中/bin/sh通常是一个符号链接,指向bash(CentOS/Ubuntu 桌面)或dash(Debian/Ubuntu 服务器)。理解sh与bash的差异是写可移植脚本的关键。
核心差异:Bash 提供很多扩展功能(数组、
[[ ]]、<<<here-string 等),sh必须保持POSIX 兼容,更严格。
🔰 简介
| 项目 | 说明 |
|---|---|
| 命令全称 | Bourne Shell(POSIX shell) |
| 常见实现 | bash --posix、dash、busybox sh、ash、ksh |
| 标准 | POSIX 1003.1(Shell & Utilities) |
| Debian 系 | /bin/sh→/bin/dash(更轻、更快、严格 POSIX) |
| RHEL/CentOS | /bin/sh→/bin/bash(bash 模拟 POSIX) |
| Ubuntu 桌面 | /bin/sh→/bin/dash |
| 启动文件 | /etc/profile、~/.profile(不读~/.bashrc) |
| Shebang 写法 | #!/bin/sh通用、严格#!/bin/bash扩展、便捷 |
| 启动速度 | dash 极快,bash 较慢 |
不同发行版的 /bin/sh 实现:
| 发行版 | /bin/sh → | 特点 |
|---|---|---|
| CentOS 7 | bash | bash 启动时检测 SHELL 变量,进入 POSIX 模式 |
| CentOS 8+ | bash | 同上 |
| Ubuntu 16.04+ | dash | 严格 POSIX,更快,更小(150KB vs 1MB) |
| Debian 10+ | dash | 同上 |
| Alpine Linux | ash | 极简,常用于 Docker 镜像 |
| FreeBSD | tcsh | BSD 系默认 |
为什么 Debian 把 sh 换成 dash?
- 性能:dash 启动约 10ms,bash 约 50-100ms,对启动脚本意义重大
- 内存:dash 仅占 150KB,bash 占 1MB+
- POSIX 严格性:dash 不支持 bash 特有语法,强制脚本可移植
- 脚本加速:系统启动时跑成百上千的 sh 脚本,dash 提速明显
📖 语法
sh[选项]脚本文件[参数...]sh-c命令字符串⚙️ 选项
| 选项 | 说明 |
|---|---|
-c CMD | 从字符串执行命令(最常用:sh -c 'cmd',适合复杂命令) |
-i | 交互模式(提示符$) |
-l/--login | 登录 shell(读/etc/profile、~/.profile) |
-s | 从标准输入读取命令 |
-x | 调试模式:每条命令执行前先打印(+前缀),再执行 |
-v | 详细模式:读到的每行原样打印(执行前) |
-e | 遇错立即退出(errexit) |
-n | 仅语法检查,不执行 |
-u | 未定义变量报错(nounset) |
-o OPTION | 设置选项(同set) |
-a | 导出所有变量 |
-f | 关闭通配符 |
-h | 哈希命令位置(默认) |
-k | 命令参数全部当作赋值 |
-t | 执行一条命令后退出 |
-D/--dump-strings | 打印所有""字符串(不执行) |
-- | 选项结束标志 |
--help/--version | 帮助/版本 |
💡 实战示例
示例 1:sh -c 字符串执行(最常用)
# ========== 1. 单条命令 ==========$sh-c'echo Hello from sh'Hello fromsh# ========== 2. 多条命令(用 ; 或换行) ==========$sh-c'cd /tmp; ls; pwd'file1.txt file2.log /tmp# ========== 3. 复杂逻辑(for/while/if) ==========$sh-c' for i in 1 2 3; do echo "Number: $i" done 'Number:1Number:2Number:3# ========== 4. 管道与重定向 ==========$sh-c'cat /etc/hostname | tr a-z A-Z'WEB01# ========== 5. sudo + sh ==========$sudosh-c'echo "root ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers'# 双引号内是 sudo 执行的命令# ========== 6. ssh 远程执行 ==========$sshuser@host"sh -c 'ls /tmp'"# 等价$sshuser@host'ls /tmp'# 复杂命令推荐 -c# ========== 7. 嵌入到 find/xargs ==========$find.-name"*.sh"-execsh-c'echo "脚本: $0"'{}\;脚本: ./a.sh 脚本: ./b.sh示例 2:sh 调试 - x / v / n
# ========== 1. -x 调试模式(最常用) ==========$catdemo.sh#!/bin/shname="Alice"echo"Hello,$name"ls/nonexistent2>/dev/null $sh-xdemo.sh +name=Alice +echo'Hello, Alice'Hello, Alice +ls/nonexistent# 不存在的文件无输出# 关键:`+` 行的就是**实际执行**的命令# 常用于:定位"脚本卡在哪里"# ========== 2. -v 详细模式 ==========$sh-vdemo.sh#!/bin/shname="Alice"+name=Aliceecho"Hello,$name"+echo'Hello, Alice'Hello, Alicels/nonexistent2>/dev/null +ls/nonexistent# 先打印**源代码**行(无 +),再执行(带 +)# ========== 3. -n 仅语法检查(不执行) ==========$catsyntax_error.sh#!/bin/shif[$x==1]# 缺 thenecho"x is 1"fi$sh-nsyntax_error.sh syntax_error.sh: line3: syntax error: unexpected end offile# 退出码 2$echo$?2# 完整命令(带错误)$catsyntax_ok.sh#!/bin/shforiin123;doecho"$i"done$sh-nsyntax_ok.sh $echo$?0# 无输出 = 语法正确# ========== 4. 组合使用 -n -v -x ==========$sh-nvxscript.sh# 三重输出:源码 → 语法检查 → 执行跟踪# 生产环境一般只用 -n 或 -x示例 3:sh vs bash 关键差异
# ========== 差异 1:[[ ]] vs [ ] ==========# bash 支持 [[ ]]$bash-c'[[ "abc" == *b* ]] && echo match'match# sh 不支持 [[ ]],必须用 [ ] + 模式匹配$ dash-c'[[ "abc" == *b* ]] && echo match'dash:1:[[: not found# dash 报错$ dash-c'case "abc" in *b*) echo match;; esac'match# dash 用 case 或 [ "$x" = "$y" ](**单个等号**)# ========== 差异 2:数组 ==========# bash 支持数组$bash-c'arr=(a b c); echo ${arr[1]}'b# dash 不支持数组$ dash-c'arr=(a b c); echo ${arr[1]}'dash:2: Bad substitution# 用 set 替代$ dash-c'set -- a b c; echo $2'b# ========== 差异 3:函数定义 ==========# bash 两种写法都行$bash-c'function foo() { echo hi; } ; foo'hi# dash 仅 function 关键字或 () 均可$ dash-c'foo() { echo hi; } ; foo'hi# ========== 差异 4:here-string <<< ==========# bash 支持$bash-c'cat <<< "hello"'hello# dash 不支持$ dash-c'cat <<< "hello"'# 无输出(here-string 被忽略)# 改用 echo + pipe$ dash-c'echo "hello" | cat'hello# ========== 差异 5:$RANDOM、$SECONDS ==========# bash 特有$bash-c'echo $RANDOM'20315$ dash-c'echo $RANDOM'$RANDOM# dash 输出字面量# ========== 差异 6:花括号扩展 ==========# bash 支持$bash-c'echo {1..5}'12345$ dash-c'echo {1..5}'{1..5}# 字面输出# 改用 seq$ dash-c'seq 1 5'12345示例 4:脚本可移植性 - shebang 选择
# ========== 选择建议 ==========# 场景 1:写给**任何 Unix** 跑的脚本(最严格)#!/bin/sh# 优点:POSIX 100% 兼容,Bash/Dash/Ksh/Busybox sh 都能跑# 缺点:少很多便利语法# 适用:运维基础脚本、init 脚本、系统级脚本# 场景 2:写给**现代 Linux** 的便利脚本#!/bin/bash# 优点:能用 [[ ]]、数组、关联数组、<<<# 缺点:不能跑到 FreeBSD 默认 sh# 适用:开发脚本、桌面脚本、CI 脚本# 场景 3:明确"用 dash"#!/bin/dash# 强制 dash,可移植性同 /bin/sh 但启动更快# 场景 4:明确"用 bash"#!/usr/bin/env bash# 推荐写法:在 PATH 找 bash,跨环境更稳实际脚本示例:
# ========== 可移植脚本(sh) ==========$cat/opt/scripts/portable.sh#!/bin/sh# 适用于所有 POSIX shell# 严格 set 选项set-eu# 错就退出 + 未定义变量报错name="${1:-anonymous}"echo"Hello,$name"# 数组用 set 模拟set-- apple banana cherryecho"第二个水果:$2"# for 循环forfin/etc/*.conf;doecho"配置文件:$f"done# if 用单个 =(POSIX 严格)if["$name"="root"];thenecho"管理员"elseecho"普通用户"fi# ========== Bash-only 脚本 ==========$cat/opt/scripts/bash_only.sh#!/bin/bash# 用 [[ ]] 比较if[["$name"=="root"]];thenecho"管理员"fi# 数组arr=(a b c)echo"${arr[1]}"# here-stringgrep"pattern"<<<"$text"# 关联数组declare-Auser_age user_age[alice]=30echo"${user_age[alice]}"# 字符串切片str="hello world"echo"${str:6:5}"# world# 进程替换diff<(lsdir1)<(lsdir2)示例 5:登录 sh 与交互 sh
# ========== 交互 sh(输入 sh 后进入) ==========$sh-i$echo$0sh# 当前 shell 是 sh$echo$SHELL/bin/bash# 注意:$SHELL 是登录 shell(用户的默认),$0 是当前实际 shell$sh-il# -l 登录模式,读 /etc/profile 和 ~/.profile$echo$PS1$# sh 的默认提示符# 不读 ~/.bashrc$ls.bashrc .profile .bashrc .profile# 实际只读 .profile# ========== 指定用户登录 ==========$sudo-i# 等价 sudo su -# 触发登录 shell$sh$echo$0sh# ========== 修改用户的登录 shell ==========$ chsh-s/bin/dash alice# 把 alice 的默认 shell 改为 dash# 下次登录 alice 时$sshalice@host $echo$0-dash# dash 启动(- 是登录标记)示例 6:sh 在系统脚本中的角色
# ========== 1. 系统启动脚本都用 sh ==========$ls/etc/init.d/*|head-3/etc/init.d/NetworkManager /etc/init.d/README /etc/init.d/abrtd $head-1/etc/init.d/NetworkManager#!/bin/sh# 全部用 /bin/sh# ========== 2. 包管理钩子 ==========$ls/etc/dhcp/dhclient-exit-hooks.d/ $head-1/etc/dhcp/dhclient-exit-hooks.d/debug#!/bin/sh# ========== 3. Cron 默认 shell ==========$cat/etc/crontabSHELL=/bin/sh# cron 任务用 sh 执行# ========== 4. systemd 服务 ExecStart 默认可执行文件 ==========$cat/etc/systemd/system/myapp.service[Service]ExecStart=/bin/sh-c"exec /opt/myapp/bin/server"# 用 sh -c 包装复杂命令# ========== 5. 用户 crontab 任务可显式指定 ==========$crontab-e*/5 * * * * /bin/sh /opt/scripts/backup.sh# 显式指定 sh 避免环境差异示例 7:sh -c 的安全与转义
# ========== 1. 嵌套 sh -c ==========$sh-c'sh -c "echo nested"'nested# ========== 2. 引号转义 ==========$sh-c'echo "Double quotes"'Double quotes# 内部双引号会被 sh -c 的命令字符串识别# 复杂场景$sh-c"echo '$(date)'"2024年1月15日 星期一14:30:00 CST# 外部是双引号 → $() 会被**当前 shell** 解释# 想要 sh -c 内解释 $()$sh-c'echo "$(date)"'2024年1月15日 星期一14:30:00 CST# 内部单引号 → 内部 sh 解释 $()# ========== 3. 性能对比 ==========# 启动速度(单位:ms)$timedash-c'true'real 0m0.002s# 2ms$timebash-c'true'real 0m0.015s# 15ms# 对 1000 个 sh -c 调用:# dash:2 秒# bash:15 秒# 这是 Debian 改用 dash 的核心理由# ========== 4. 函数内调用 sh -c ==========my_func(){localresult=$(sh-c"echo$1")echo"Result:$result"}# 注意:$1 是外层 shell 解释# 想要 sh -c 内部解释:改用 'echo "$0"'示例 8:写真正的 sh 脚本(POSIX 严格)
$cat/opt/scripts/posix_strict.sh#!/bin/sh# 严格 POSIX 脚本 - 跨 Unix 兼容# 1. 严格模式set-eu# -e: 任何命令失败立即退出# -u: 使用未定义变量报错# (可加 -o pipefail 但 POSIX 不保证)# 2. 变量赋值(无空格)name="Alice"# 或name=Alice# 没有空格就能省略引号# 3. 命令替换(POSIX 用 ` `,不是 $())date1=`date+%F`date2=$(date+%F)# $() 是 POSIX 标准,更推荐# 4. 条件判断([ = 单等号,POSIX 严格)if["$name"="Alice"];thenecho"Match"fi# 5. for 循环(不依赖 bash 扩展)forfin*.txt;do[-e"$f"]||continue# 处理空目录echo"File:$f"done# 6. while 循环n=0while["$n"-lt5];doecho"n =$n"n=$((n+1))# POSIX 算术done# 7. 退出码exit_code=$?if["$exit_code"-ne0];thenecho"Failed with code:$exit_code"exit"$exit_code"fi# 8. 函数my_log(){level=$1shiftecho"[$(date'+%F %T')] [$level]$*">&2}my_log"INFO""Application started"# 9. 错误处理(trap)cleanup(){echo"Cleaning up..."rm-f/tmp/myapp.lock}trapcleanup EXIT INTTERM# 10. 读文件whileIFS=read-rline;doecho"Line:$line"done</etc/hostname# 测试可移植性$ dash /opt/scripts/posix_strict.sh $bash--posix/opt/scripts/posix_strict.sh $ busyboxsh/opt/scripts/posix_strict.sh# 三者都能跑 = 真正可移植⚠️ 注意事项
1. /bin/sh 不一定指向同一程序
Debian/Ubuntu 系指向dash,CentOS/RHEL 系指向bash。脚本可移植必须用 POSIX 语法。
2.
sh -x调试会暴露变量$secret="mypassword"$sh-xscript.sh +secret=mypassword# ⚠️ 密码会显示调试敏感脚本前先脱敏。
3.
sh与bash启动文件不同
- sh:
/etc/profile、~/.profile- bash:
/etc/profile、~/.bash_profile、~/.bashrc
在 sh 登录时,~/.bashrc 不会被读。
4. -e 选项的边界
set-efalse# 立即退出echo"never prints"但
if false; then不触发(条件命令不算错)。
与set -o pipefail配合防止管道错失。
5. dash 的 “echo -e” 不支持
$ dash-c'echo -e "a\nb"'-ea b# 把 -e 当字面量dash 的
echo是内建,BSD 风格。改用printf:$ dash-c'printf "a\nb\n"'a b
6.
set -u与$@冲突set-uecho"$@"# 没参数会报错正确写法:
echo"${@:-}"# 没参数时用空字符串
7. sh -c ‘多行’ 的换行处理
$sh-c'echo a; echo b'a b $sh-c"echo a echo b"aechob# 字面输出,因为外层双引号未执行 sh 解析换行复杂多行用 heredoc 传给 sh。
8. shebang 的查找路径
#!/bin/sh是绝对路径;#!/usr/bin/env sh用PATH 查找。后者更可移植(Mac 默认没/bin/sh,但有/usr/bin/env)。
9.
sh收到的 HUP 信号
父 shell 退出时,子 sh 会继承 HUP。
nohup sh script.sh &免疫 HUP。
**10. dash 与 bash 的R A N D O M ∗ ∗ d a s h 无 ‘ RANDOM** dash 无 `RANDOM∗∗dash无‘RANDOM
,$RANDOM` 字面输出。生产脚本别用这个变量。
📝 总结
sh是 POSIX 标准的 Shell,是 Linux 系统脚本的"通用语"。本节要点:
- 核心概念:
/bin/sh是符号链接,Debian 系指dash,RHEL 系指bash兼容模式 - 关键选项:
sh -c 'cmd'(字符串执行)sh -x script(调试)sh -n script(语法检查)sh -l(登录模式)
- POSIX 严格:
[ "$x" = "$y" ](单等号)- 用
case或[ ]不用[[ ]] - 用
set --模拟数组 - 避免
<<<、扩展{1..5}、bash 特有变量
- 写脚本选择:
- 系统级 / 跨 Unix:
#!/bin/sh - 现代 Linux 便利:
#!/usr/bin/env bash
- 系统级 / 跨 Unix:
- 调试三件套:
-x(执行跟踪)→-n(语法检查)→-v(详细输出)
最终建议:写新脚本,如果只是内部工具,
#!/bin/bash即可;写给运维/系统/嵌入式,用#!/bin/sh+ 严格 POSIX 语法;不确定,先shellcheck script.sh(静态分析工具)检查兼容性。
📚 相关命令
| 命令 | 用途 |
|---|---|
bash | Bourne-Again Shell(扩展最多的 sh) |
dash | Debian Almquist Shell(POSIX 严格) |
ash | Almquist Shell(Busybox 嵌入式) |
ksh | Korn Shell(商业传统) |
zsh | Z Shell(macOS 默认) |
fish | Friendly Shell(用户友好) |
csh/tcsh | C Shell(BSD) |
busybox sh | 嵌入式 Linux 最小 shell |
shellcheck | Shell 脚本静态分析(强烈推荐) |
set | shell 内置:设置/取消选项 |
env | 执行命令并可设置环境变量 |
exec | 替换当前进程 |
source/. | 当前 shell 读取并执行(不同 sh 行为可能差异) |
