Composer 本身无 exec 命令,应使用 composer run-script 执行自定义脚本;scripts 中字符串命令由 Composer 包装为 shell 函数执行,非直传 execve,存在参数切分、退出码丢失、shell 特性受限等问题;PHP 的 exec() 函数需用数组传参、捕获 $return_var 退出码,并避免 shell 注入。

Composer 本身没有 exec 命令;你真正想用的是 composer run-script 执行自定义脚本,或直接调用系统 exec(如 PHP 的 exec() 函数)——但这两者完全不是一回事。混淆它们是线上 CI 失败、本地调试失灵的常见起点。
Composer scripts 里写命令 ≠ 直接 exec
你在 composer.json 的 scripts 字段里写的字符串,比如 "lint": "phpcs src/",会被 Composer 包装成一个 shell 函数再执行,而不是原样传给 execve()。这意味着:
- 参数不会按空格严格切分:如果
src/路径含空格,phpcs收到的可能是多个碎裂参数,而非一个完整路径 - 退出码默认被吞掉:子命令返回 1,
composer run-script lint仍可能返回 0,CI 就不会中断 - shell 特性受限:管道
|、重定向>可用,但变量展开、条件判断等依赖宿主 shell,容器里常出问题
PHP 的 exec() 函数怎么安全传参
如果你在 PHP 脚本里调外部程序(比如 csvtool),别拼字符串,尤其别把用户输入直接塞进 $command。空格、单引号、反引号都会触发 shell 解析歧义或命令注入。
- 路径含空格?用双引号包裹整个可执行路径:
"C:\Program Files\Tool\app.exe"(Windows)或"/opt/my tool/bin/run"→ 改成"/opt/my tool/bin/run"不行,得写成'"/opt/my tool/bin/run"'再拼进命令串 - 更稳的做法是绕过 shell:用
proc_open()或封装库(如ghostwriter/shell),把程序路径和参数拆成数组传入,不经过/bin/sh解析 - 必须捕获退出码:第三个参数
$return_var是唯一可靠来源,exec('ls /missing', $out, $code)后立刻检查$code === 2(No such file)还是127(command not found)
为什么 exit code 127 总是出现
这不是你的脚本错了,而是系统根本找不到你要执行的命令。常见原因:
-
PATH在 Web SAPI(如 Apache mod_php)和 CLI 下不一致:Web 环境里which csvtool可能返回空,而终端里能查到 - 用了 bash 特性(如
[[ ]]、source),但宿主 shell 是dash(Debian/Ubuntu 默认/bin/sh) - 容器镜像精简过度,连
/bin/sh都被删了,或者execve()直接失败却没报错 —— 此时$return_var可能仍是 0,得靠2>&1捕获 stderr 判断
最易被忽略的一点:Composer 的 @ 前缀只透传**直接命令**的退出码,对 sh -c 'cmd1 && cmd2' 这类包装无效;而 PHP 的 exec() 第三个参数必须显式传入,漏写就等于放弃错误判断依据。











