侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 259 篇文章
  • 累计收到 7 条评论

execFile 保住参数边界,exec 会交给 shell 重读

2026-11-4 / 0 评论 / 7 阅读

execFile 保住参数边界,exec 会交给 shell 重读

一段路径里有空格,交给子进程后却变成两个参数;换成 exec,单引号和 $HOME 看起来像被解释了。差异不在空格本身,而在命令有没有经过 shell。

下面的实验只调用系统自带的 printf,输入也是固定字符串。没有拼接外部输入,没有执行删除、写入或网络命令。代码跑在本机 Node v26.8.1、macOS 上。为便于复现,脚本只依赖 Node 标准库和系统 printf,没有安装第三方包;环境变量展开演示读取的是当前进程的 HOME。

数组把边界带到进程

execFile(file, args) 接收可执行文件路径和参数数组。每个数组元素都是一个独立参数,Node 直接启动程序,不会先把它们合成一行 shell 命令。参数里这些字符会原样交给程序,Node 不会先把它们解释成 shell 语法。

这段代码用 printf 打印每个参数,尖括号标出边界:

import { execFile } from 'node:child_process';
import { promisify } from 'node:util';

const run = promisify(execFile);
const result = await run('/usr/bin/printf', [
  '<%s>|',
  'red blue',
  '$HOME; *.txt',
]);
console.log(result.stdout);

%s 每次消费一个参数。输出里的 red blue 在同一对尖括号内;$HOME; *.txt 也原样出现。Node 不会把它们当作 shell 变量、命令分隔符或通配符。

同样的边界也适用于启动 Node 子进程:把代码和每个业务值分别放进参数数组,不必自己拼引号。调用方仍要校验参数的含义和长度,参数数组解决的是 shell 解析,不是业务输入验证。

exec 收到的是一整行

exec(command) 的参数是命令字符串。在 macOS 和常见 Unix 环境里,Node 会把命令交给 shell。shell 读取整行后按自己的规则拆分单词;变量也会按引号规则展开,通配符同样可能被处理。平台和 shell 不同,具体行为也可能不同。

同样用 printf 做对照:exec 的固定命令写成 /usr/bin/printf '<%s>|' red blue。red 和 blue 被 shell 拆成两个参数,输出自然是 <red>|<blue>|。这不是 printf 对空格做了特殊处理,参数边界在 shell 解析时就已经改变了。

变量展开也能在不执行任何危险操作的情况下看清楚。命令字符串里用双引号包住 $HOME,shell 会把它替换成当前环境的目录;对应的 execFile 参数 $HOME; *.txt 则保持字面内容。shell 的引号与转义语法会改变解析结果。手工转义很容易漏掉某个平台或某个字符组合。

实测输出原样保存在 tools/verify-execfile-shell-boundary.log。运行脚本是 tools/verify-execfile-shell-boundary.mjs:

node=v26.8.1
platform=darwin
execFile spaced argument="<red blue>|"
exec unquoted words="<red>|<blue>|"
execFile shell syntax stays literal="<$HOME; *.txt>|"
exec double-quoted HOME expansion="</Users/yxh>|"
execFile node argv=["red blue","$HOME; *.txt"]

这里没有把 ; 后面接成第二条命令。它只是作为一个固定参数传给 execFile;exec 的演示也只用了固定的 printf 和 $HOME。差异已经足够明显,不需要用危险输入来证明 shell 会解析 shell 语法。

边界也有例外

execFile 的 shell 选项默认关闭。显式设成 true 后,调用又会经由 shell,参数安全属性也就不能再按默认情况推断。execFile 默认还会把 stdout、stderr 缓存在内存里,输出过大时会碰到 maxBuffer 限制;长时间、大量输出的任务通常更适合 spawn,边读流边处理。

exec 适合命令本身就是可信、固定的 shell 表达式,且确实需要管道、重定向或 shell 内建语法的情况。可执行文件和参数都能明确拆开的普通调用,我会选 execFile。尤其是值来自请求、配置或文件时,不要把它直接拼进 exec 命令字符串;改用参数数组,并对业务允许的值做白名单或格式校验。

    🤞 分享