代码 Bash 5.3:用 ${ cmd; } 在循环里取字符串,不再 fork 子进程

2026-11-14 21:05:33

Bash 5.3:用 ${ cmd; } 在循环里取字符串,不再 fork 子进程

x=$(cmd) 每次都会 fork 一个子 shell。循环几千次,就是几千次 fork;更麻烦的是,子 shell 里的变量改动全部丢失:

count=0
for f in *.txt; do n=$(count=$((count+1)); echo $count); done
printf '%s\n' "$count"   # 永远是 0

Bash 5.3 增加了两种在当前 shell 执行、不 fork 子 shell 的命令替换写法。

两种形式的差别

${ cmd; }:命令在当前 shell 执行,stdout 照常被捕获,尾部换行和 $( ) 一样会被剥掉。

${| cmd; }:不捕获 stdout,取的是命令执行完后 REPLY 的值。也就是由命令自己给 REPLY 赋值,所以结果不受尾部换行剥离影响,命令顺带打印的调试信息也不会混进结果:

name=${| REPLY=${f%.tar.gz}; }

简单说,${| ...; } 适合“算一个值”,${ cmd; } 适合直接复用现成命令的 stdout。

能省 fork 的原因:命令就在当前 shell 进程里跑,不 fork、不建管道,变量赋值、printf -v、数组改动都留在原地。上面的计数可以写成:

count=0
for f in *.txt; do
  n=${ count=$((count+1)); printf '%s' "$count"; }
  printf '%d: %s\n' "$n" "$f"
done
printf 'total=%d\n' "$count"   # 这次是真的累加

什么时候不能用

没有隔离,命令的所有副作用都会落到调用者身上:

  • cd、exec、set -e、改 IFS、shopt、trap 都会改变外层 shell 的状态;命令里一句 exit 直接退掉整个脚本。
  • 依赖独立进程环境的命令(自己再开子进程、改 umask、期望干净的 fd 表)行为可能和子 shell 版本不一致。
  • 并发与信号语义变了:里面起的后台任务进的是当前 shell 的作业表,wait、SIGCHLD、ERR trap 的触发时机跟子 shell 写法不同。
  • 这是 bash 扩展语法,sh 和旧版 bash 直接报语法错误,发布脚本得先检测。

版本检测

if (( BASH_VERSINFO[0] < 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] < 3) )); then
  printf 'need bash >= 5.3, got %s\n' "$BASH_VERSION" >&2
  exit 1
fi

更保险的做法是特性探测:bash -c 'x=${ printf ok; }' 2>/dev/null。

GLOBSORT 与 read -E

GLOBSORT 控制路径名展开和补全结果的排序,取值有 name、size、blocks、mtime、atime、ctime、numeric、none,值前加 +/- 表示升序/降序:

GLOBSORT=-mtime
printf '%s\n' *.log      # 最新的排最前

它只改 shell 自己的展开顺序,ls 仍按自己的规则排序。

read -E 让 read 走 readline 并启用默认的 bash 补全(含可编程补全),交互式脚本里收路径很实用:

read -E -p 'log file: ' f

其它值得留意的地方

  • compgen -V varname 把补全结果写进变量而不是 stdout。
  • source -p PATH 用给定 PATH 找文件;trap -P 打印信号当前绑定的动作。
  • 新增动态变量 BASH_MONOSECONDS(单调时钟)和 BASH_TRAPSIG(当前 trap 的信号号)。
  • printf %q/%Q 的 alternate form 强制单引号;%ls/%lc 在多字节 locale 下按宽字符处理。
  • [[ ]] 正则编译失败会打印错误,不再静默。
  • shopt array_expand_once 取代 assoc_expand_once;MULTIPLE_COPROCS 默认启用。
  • 不兼容点:test 内建在括号子表达式且参数超过 4 个时,解析行为对齐 coreutils;交互式 shell 在 source 脚本时不再打印作业通知。
  • Readline 8.3 带来 search-ignore-case、execute-named-command、export-completions、rl_reparse_colors;源码要求 C23,不再支持 K&R 编译器。
复制全文 生成海报 Shell Bash 脚本

推荐文章

程序员茄子在线接单