Composer脚本执行外部Shell脚本易失败,因默认用/bin/sh且不加载用户环境;应确保权限、用相对或绝对路径、避免bash特性,并优先用PHP封装调用。

Composer脚本里直接执行外部 Shell 脚本会失败
Composer 的 scripts 字段默认走的是 PHP 进程的 shell 执行器(类似 proc_open),它不自动加载用户 shell 环境(比如 ~/.bashrc),也不保证当前工作目录是项目根目录。直接写 ./deploy.sh 或 sh ./scripts/build.sh 很容易报 sh: 1: ./deploy.sh: Permission denied 或 No such file or directory。
- 确保外部脚本有可执行权限:
chmod +x scripts/deploy.sh - 路径必须用绝对路径或相对于 Composer.json 所在目录的相对路径;
scripts不会自动从vendor/bin或$PATH查找 - 避免依赖
source、export或 bash 特有语法(Composer 默认调用/bin/sh,不是bash) - 如果脚本需要读取当前 Git 分支、环境变量等,建议在脚本开头显式
cd "$(dirname "$0")/.."切到项目根目录
用 php 调用比直接写 shell 命令更可控
在 composer.json 里定义 script 时,优先用 php -r 或封装成 PHP 命令,能绕过 shell 解析歧义和权限问题:
"scripts": {
"deploy": "php -r "exec('./scripts/deploy.sh > /dev/null 2>&1 &'); echo 'Deploy started in background\n';""
}
这样做的好处:PHP 进程明确知道当前工作目录是 Composer.json 所在位置,且不会被 shell 的 cd 或子 shell 隔离影响。
- 若需等待脚本完成,去掉
&并用system()替代exec() -
php -r中双引号需转义内部双引号,也可改用单引号包裹整个命令,内部用双引号 - 错误输出重定向建议保留:
2>&1,否则失败时看不到报错
symfony/console 命令比纯 shell 更适合复杂逻辑
一旦外部脚本开始做条件判断、参数解析、多环境适配,硬塞进 Composer script 就难维护。这时应把逻辑移到 PHP 命令中,再由 Composer 调用:
"scripts": {
"build": "php bin/build.php"
}
bin/build.php 可以 require 项目 autoload,调用 shell_exec('./scripts/real-build.sh'),还能记录日志、校验参数、提前退出。
- PHP 脚本第一行加
#!/usr/bin/env php,并chmod +x,就能像普通命令一样运行 - 用
getenv('COMPOSER_DEV_MODE')区分开发/生产行为,比在 shell 里判断$COMPOSER_DEV_MODE更可靠 - 别在 PHP 脚本里用
exit()返回非 0 值以外的数字——Composer 会把它当异常,但不会显示具体退出码
Windows 下 .bat 和 .ps1 的兼容陷阱
Composer 在 Windows 上默认尝试运行 .bat,但如果你写了 .sh,它不会自动调用 git-bash 或 wsl。强行指定 bash ./script.sh 会导致找不到 bash 命令。
- 跨平台方案:统一用 PHP 脚本封装,内部用
PHP_OS_FAMILY === 'Windows'分支处理 - 若坚持用批处理,文件名必须是
.bat,且不能含空格或 Unicode 字符(CMD 解析会失败) -
.ps1默认被系统策略禁止执行,即使加了PowerShell -ExecutionPolicy Bypass,CI 环境也常不生效


















