Shell脚本集成进Composer scripts不推荐直接写长命令链,因路径、环境变量(如PATH不含node_modules/.bin)、跨平台语法(如Windows不支持&&)及子shell上下文不可控等问题易致失败;应封装为独立shell文件,显式处理cd、set -e、退出码与换行符。

Shell 脚本集成进 Composer scripts 是可行的,但容易因路径、环境、错误传播等问题失败;不推荐直接写长命令链,应优先封装为独立可测试的 shell 文件,并显式处理退出码和目录锚定。
为什么直接在 scripts 里写 shell 命令常出错
常见现象:npm run build 找不到命令、chmod 权限失败、rsync 排除规则不生效、Windows 下 && 报语法错误。根本原因不是 Composer 本身限制,而是 shell 执行上下文不可控:
- 当前工作目录虽是项目根,但
$PWD在子 shell 中可能未继承(尤其 macOS / Git Bash) - PATH 环境变量不含
node_modules/.bin,npm或php-cs-fixer直接报 command not found - 单行字符串如
"npm ci && npm run build"在 Windows CMD 中&&不被识别,会卡在第一个命令后静默退出 - 前一条命令失败(如
npm ci因网络中断退出码 1),&&后的命令仍不执行——这看似合理,但你无法捕获错误日志或做清理
怎么安全地集成外部 shell 脚本
把逻辑抽成独立文件,再由 Composer 调用,是最稳的做法。例如创建 scripts/build-frontend.sh:
#!/usr/bin/env bash set -e # 任一命令失败即退出 cd "$(dirname "$0")/.." || exit 1 npm ci --no-audit npm run build cp -r public/build/* ../public/build/ 2>/dev/null || true
然后在 composer.json 中注册:
"scripts": {
"build:frontend": ["bash scripts/build-frontend.sh"]
}-
set -e是必须加的,否则脚本内某步失败会被忽略 -
cd "$(dirname "$0")/.."确保无论从哪调用,都回到项目根目录 - 避免用
./node_modules/.bin/npm—— 改用系统 PATH 中的npm,并确保 CI 环境已预装 Node.js - 别在脚本里写
exit 0结尾:Composer 依赖进程退出码判断成功与否,非零即失败
自动执行时怎么区分 install 和 update 场景
post-install-cmd 和 post-update-cmd 都会触发 shell 脚本,但你不总想每次 composer install 都跑构建或清缓存。靠 $COMPOSER_COMMAND 环境变量判断:
"post-install-cmd": [
"@php -r \"if (getenv('COMPOSER_COMMAND') === 'update') { system('bash scripts/deploy-post.sh'); }\""
]注意点:
- PHP 单行命令必须用双引号包裹整个
-r参数,否则 shell 会提前解析= -
COMPOSER_COMMAND值只有install或update,不会是空或其它值 - 不要混用
COMPOSER_DEV_MODE:它只表示是否启用了 dev 依赖,和触发时机无关 - 如果脚本本身是 shell 文件,就别再套一层
@php -r,直接写"post-update-cmd": ["bash scripts/on-update.sh"]更清晰
生产部署中 rsync + vendor 的坑怎么绕
用 rsync 推代码到生产机时,vendor/ 必须排除,且不能依赖本地 vendor/ 复制过去——因为 PHP 版本、扩展、--classmap-authoritative 等配置不同,直接复制大概率导致类加载失败或性能暴跌。
-
rsync -avz --exclude="vendor/" --exclude="node_modules/" ./ user@prod:/var/www/app/是底线 - 远程执行的
composer install必须带--no-dev --optimize-autoloader --classmap-authoritative - 别在本地脚本里写
rsync ... && ssh user@prod 'cd /var/www/app && composer install':一旦 rsync 成功但 ssh 失败,状态就残了;拆成两个独立脚本(deploy:sync和deploy:install)更利于 CI 分步重试 - 如果要用 rsync 同步
storage/或public/uploads,记得加--delete和--chmod=Du=rwx,Dgo=rx,Fu=rw,Fgo=r控制权限,避免 Web 服务器无权读写
最易被忽略的是:所有 shell 脚本必须用 Unix 换行符(LF),Windows 的 CRLF 会导致 #!/usr/bin/env bash 解析失败,报 bad interpreter: No such file or directory —— 这个错误不提示具体哪行,只显示脚本路径,排查成本极高。


















