Shell脚本精读 · S09-03 | 子 Shell 与命令替换:`()` 与 `$()`

📅 2026/7/8 12:32:09
Shell脚本精读 · S09-03 | 子 Shell 与命令替换:`()` 与 `$()`
模块S09 进程、管道与重定向篇号S09-03 / 42预计阅读50 分钟主线Bash文章目录本篇目标30 秒速览正文1. 三种括号别混了2. 命令替换$(...)3. 旧式反引号 ... 4. 引号与词拆分5. 末尾换行会被去掉6. 子 shell( ... )7. 命令替换也在子进程里跑8. 经典模式SCRIPT_DIR9. 退出码与 set -e10. 嵌套与多行11. 与管道子 shell 的对照12. 读脚本检查清单练习判断题实操题改错题下一篇预告本篇目标掌握命令替换$(命令)与旧式命令以及子 shell( 命令 )。能区分$( )、$(( ))、( )、{ }理解「输出当文本」「子进程里改的变量父 shell 看不见」并读懂SCRIPT_DIR...、for x in $(...)等高频写法及其坑。30 秒速览$(cmd)执行cmd用其stdout 文本替换到命令行命令替换。cmd与$(cmd)等价难嵌套、难读新脚本用$()。( cmd )子 shell分组cd、赋值一般不影响父 shell。$(( expr ))是算术展开不是命令替换S05-04。替换结果未加引号会再经历词拆分S02-03。末尾换行命令替换会去掉 stdout尾部换行仅尾部。正文1. 三种括号别混了写法名称作用$(cmd)命令替换跑命令输出字符串插进命令行$((n1))算术展开算整数不跑外部命令( cmd )子 shell在子进程里跑一组命令{ cmd; }命令组当前 shell非子进程需;或换行echo$(date%F)# 命令替换2026-05-19echo$((12))# 算术3(cd/tmp;pwd)# 子 shell 里 cdpwd# 仍在原目录2. 命令替换$(...)today$(date%Y-%m-%d)files$(ls*.sh2/dev/null)echotoday$today过程启动date子 shell / 子进程环境执行收集stdout成一段文本替换$(...)所在位置stderr 不会进入替换结果除非21合并进 stdoutout$(greppattern missing2/dev/null)# out 为空grep 失败退出码 1 不影响赋值本身是否成功见下文 set -e3. 旧式反引号...todaydate%Y-%m-%d与$(date %Y-%m-%d)等价。缺点嵌套时引号易乱可读性差# 难读echoecho\date\# 清晰echo$(echo$(date))S02-01双引号内两种都会展开单引号内都不展开。4. 引号与词拆分path$(echo/my dir/file)cp$path# 错拆成 /my dir/filecp$path# 对forfin$(ls*.log);doecho$fdone$(ls *.log)未加引号→ 输出按IFS拆成多个单词S07-01、S02-03。路径含空格、换行时会出错。更稳forfin*.log;do...done# 或 mapfile/readarray原则命令替换结果默认当字符串用就加引号$(cmd)。5. 末尾换行会被去掉$printfa\nb\n|od-c# 有末尾 \n$x$(printfa\nb\n);echo$x|od-c# 替换结果往往去掉**尾部连续换行**保留中间换行读脚本$(wc -l f)等单行数字没问题若要保留末尾空行别依赖命令替换用重定向 / 文件。6. 子 shell( ... )count0(count1;echoinner$count)echoouter$count# outer0特性子 shell( )当前 shell{ }cd不影响父目录影响变量赋值不影响父 shell影响速度略慢fork快管道右侧常在子 shellS07-02、S09-02—临时切换目录(cd$subdirmake)# 父 shell 仍留在原目录分组重定向S09-01 提过{cmd1|cmd2;}all.log21{ cmd; }不是子 shellcd会留在当前 shell——别与( )混用。7. 命令替换也在子进程里跑outer1inner$(outer2;echo$outer)echoouter$outerinner$inner# outer1 inner2$( )里的赋值不会改父 shell 变量与| while read类似都是子进程问题。8. 经典模式SCRIPT_DIRSCRIPT_DIR$(cd $(dirname${BASH_SOURCE[0]})pwd)部分作用dirname ${BASH_SOURCE[0]}当前脚本所在目录相对或绝对cd ... pwd在子 shell里 cd得到绝对路径外层$( )把 pwd 输出赋给SCRIPT_DIR父 shell工作目录不变S08-03。9. 退出码与set -eset-euopipefailx$(false)# false 非 0 → 在 Bash set -e 下脚本可能在此退出命令替换失败内部命令非 0在set -e下常被视为失败。需要容忍时x$(greppattern f||true)ifout$(may_fail);thenechook:$outfi$?after assignment检查的是赋值整条还是内部命令读脚本时注意 Bash 版本与set -e细节稳妥写法用if out$(cmd); then。10. 嵌套与多行host$(curl-fsS$url|jq-r.host)内层可以是管道S09-02$?/PIPESTATUS在赋值后要立刻查。多行可读性hash$(openssl dgst-sha256$file\|awk{print $2})11. 与管道子 shell 的对照场景子进程父 shell 见变量var$(cmd)是只见var字符串不见 cmd 里其它赋值cmd1 | cmd2右侧常是右侧循环里赋值不见( cmd )是不见{ cmd; }否见12. 读脚本检查清单是$( )还是$(( ))还是( )$(...)是否加了...防拆词是否用...嵌套难读代码cd在( )里还是裸写误改全局目录for x in $(...)是否该改成 glob /while read练习判断题date与$(date)在 Bash 中通常等价。( cd /tmp )之后父 shell 的当前目录变为/tmp。$((12))会执行名为12的命令。x$(a1; echo 2)执行后父 shell 中a为 1。命令替换会捕获命令的 stderr 作为替换文本。参考答案对。错子 shell 里 cd。错算术展开。错a1在子进程。错默认只 stdoutstderr 仍到终端或需21。实操题下面输出什么x10(x20;y30)echox$xy${y:-unset}z$(x40;echo50)echox$xz$z参考答案x10 yunset x10 z50改错题#!/usr/bin/env bashset-euopipefailforuserin$(getentpasswd|cut-d:-f1);doechouser$userdone参考用户名含空格时会拆错。可改为whileIFS:read-ruser _;doechouser$userdone(getentpasswd)或保证字段解析用read不要for ... in $(...)拆整表输出。下一篇预告S09-04《后台任务、wait与进程替换》—cmd 、wait $!、(cmd)进程替换。