如何让PHP触发的Bash脚本等待npm构建完成后再执行?
问题场景
搭建React个人网站自动化部署流程,基于Vite打包,流程为:本地推送代码至远程仓库 → Webhook触发服务器PHP脚本 → PHP调用bash脚本依次执行git拉取、npm install、npm run build、rsync同步构建产物到线上目录。
手动通过PuTTY运行bash脚本完全正常,但PHP调用时,bash脚本未等待npm install和build完成就执行rsync,导致同步的是上一次的构建结果。尝试过添加pause 120和&&链式调用,手动运行有效但PHP调用时无效。
相关代码
PHP调用bash的代码:
$commandOutput = shell_exec("/bin/bash ./sync.sh --repo=" . $hook["repo"] . " --branch=" . $hook["branch"] . " --subdomain=" . $subdomain);
bash脚本核心代码:
if [[ ${repo} && ${branch} && ${subdomain} ]]; then git -C /path/"${repo}"/ checkout "${branch}" git -C /path/"${repo}"/ fetch git -C /path/"${repo}"/ pull origin "${branch}" npm --prefix /path/"${repo}"/ install npm --prefix /path/"${repo}"/ run build; rsync -auv /path/"${repo}"/htdocs/* /path/"${subdomain}"/htdocs; else echo "repo, branch and subdomain were not provided, I will not do anything."; fi
核心原因
PHP通过shell_exec执行命令时,运行环境和手动登录PuTTY的环境不一致:
- 可能找不到正确的
node/npm路径,导致命令异常启动后台进程而非阻塞执行; - 环境变量缺失(比如
PATH),使得npm命令执行行为和手动运行时不同。
修复方案
1. 指定node/npm绝对路径
手动登录服务器,执行以下命令获取绝对路径:
which node which npm
将bash脚本中的npm替换为上述命令得到的绝对路径(例如/usr/local/bin/npm),确保PHP调用时能找到正确的执行文件:
# 替换原npm命令 /usr/local/bin/npm --prefix /path/"${repo}"/ install /usr/local/bin/npm --prefix /path/"${repo}"/ run build
2. 强制链式阻塞执行并添加错误检查
用&&连接所有命令,确保前一个命令执行成功(返回码为0)才会执行下一个,同时将输出重定向到日志文件方便排查:
if [[ ${repo} && ${branch} && ${subdomain} ]]; then git -C /path/"${repo}"/ checkout "${branch}" && \ git -C /path/"${repo}"/ fetch && \ git -C /path/"${repo}"/ pull origin "${branch}" && \ /usr/local/bin/npm --prefix /path/"${repo}"/ install && \ /usr/local/bin/npm --prefix /path/"${repo}"/ run build && \ rsync -auv /path/"${repo}"/htdocs/* /path/"${subdomain}"/htdocs else echo "repo, branch and subdomain were not provided, I will not do anything." fi
3. 确保脚本完整执行
shell_exec虽会等待命令完成,但如果bash脚本中存在后台进程可能提前返回。可在bash脚本末尾添加wait命令,确保所有子进程执行完毕:
# 在rsync命令后或脚本末尾添加 wait
或者改用PHP的exec函数捕获执行状态,方便排查:
$command = "/bin/bash ./sync.sh --repo=" . $hook["repo"] . " --branch=" . $hook["branch"] . " --subdomain=" . $subdomain; exec($command, $output, $returnVar); # 可将$output和$returnVar写入日志,用于问题排查
4. 添加日志排查
在bash脚本关键步骤添加日志输出,确认每个命令的执行状态和时间:
LOG_FILE="/var/log/deploy.log" echo "$(date '+%Y-%m-%d %H:%M:%S'): 开始执行npm install" >> $LOG_FILE /usr/local/bin/npm --prefix /path/"${repo}"/ install >> $LOG_FILE 2>&1 echo "$(date '+%Y-%m-%d %H:%M:%S'): npm install完成,返回码: $?" >> $LOG_FILE echo "$(date '+%Y-%m-%d %H:%M:%S'): 开始执行npm run build" >> $LOG_FILE /usr/local/bin/npm --prefix /path/"${repo}"/ run build >> $LOG_FILE 2>&1 echo "$(date '+%Y-%m-%d %H:%M:%S'): npm run build完成,返回码: $?" >> $LOG_FILE echo "$(date '+%Y-%m-%d %H:%M:%S'): 开始执行rsync同步" >> $LOG_FILE rsync -auv /path/"${repo}"/htdocs/* /path/"${subdomain}"/htdocs >> $LOG_FILE 2>&1 echo "$(date '+%Y-%m-%d %H:%M:%S'): rsync同步完成" >> $LOG_FILE
通过查看日志中的时间戳和返回码,可确认npm命令是否真的执行完成,以及是否存在报错。
验证方法
修改后,通过Webhook触发PHP脚本,查看/var/log/deploy.log日志,确认npm install、build完成后才执行rsync。若仍有问题,检查npm命令的返回码,非0则说明命令执行失败,需排查环境依赖或权限问题。
内容的提问来源于stack exchange,提问作者Mike Willis

