
本文介绍一种可靠方法:通过在Shell脚本中主动写入自身PID到文件,使PHP可在进程运行时准确获取并终止它,避免exec()阻塞导致取消失效的问题。
本文介绍一种可靠方法:通过在shell脚本中主动写入自身pid到文件,使php可在进程运行时准确获取并终止它,避免`exec()`阻塞导致取消失效的问题。
在Web环境中异步执行Shell脚本并支持实时中断,关键在于解耦执行与控制流——不能依赖PHP的exec()同步等待返回PID(因其会阻塞至命令结束),而应让Shell进程自行暴露其PID。以下是经过验证的生产级实现方案:
✅ 正确做法:Shell脚本自写PID文件
修改你的 runScript.sh,在第一行添加:
#!/bin/bash echo $$ > script_pid.txt # 立即写入当前Bash进程PID # 后续你的实际命令... sleep 30 # 示例:模拟长时间运行任务
⚠️ 注意:$$ 是Bash内置变量,表示当前Shell进程的PID;务必放在脚本开头,确保在任何耗时命令前完成写入。
✅ PHP启动逻辑(无阻塞、轻量)
if (isset($_POST["runBtn"])) {
// 直接执行脚本(不加 & echo $!,避免exec阻塞)
exec("bash runScript.sh > /dev/null 2>&1 &");
// 可选:简单校验PID文件是否生成
$_SESSION["PID_FILE"] = "script_pid.txt";
echo json_encode(["status" => "started"]);
}
✅ 优势:exec()立即返回,页面不卡顿;PID由脚本自身写入,100%可靠。
✅ 取消逻辑(AJAX端调用)
myFunctions.php 中的 cancelEx 方法优化如下:
case 'cancelEx':
$pidFile = 'script_pid.txt';
if (!file_exists($pidFile)) {
$aResult['error'] = 'PID file not found. Process may have finished.';
break;
}
$pid = trim(file_get_contents($pidFile));
if (empty($pid) || !is_numeric($pid)) {
$aResult['error'] = 'Invalid PID in file.';
break;
}
// 先尝试优雅终止(SIGTERM),500ms后强制(SIGKILL)
$result = shell_exec("kill $pid 2>/dev/null");
sleep(0.5);
shell_exec("kill -9 $pid 2>/dev/null");
// 清理PID文件(可选)
@unlink($pidFile);
$aResult['result'] = "Process $pid terminated.";
break;
? 关键注意事项
- 权限安全:确保Web服务器用户(如www-data)对 script_pid.txt 有读写权限,且脚本执行目录不可被恶意写入。
- PID有效性验证:使用 posix_kill($pid, 0)(PHP)或 kill -0 $pid(Shell)检查进程是否存在,避免误杀其他进程。
- 信号选择:优先发送 SIGTERM(kill $pid),给脚本清理资源的机会;仅超时后使用 SIGKILL(kill -9)。
- 并发风险:若允许多次“Run”,需为每次生成唯一PID文件名(如 script_pid_{$timestamp}.txt),避免覆盖。
- 调试建议:在脚本中追加日志 echo "Started with PID $$" >> debug.log,便于排查。
该方案彻底规避了PHP exec() 的同步阻塞缺陷,将PID管理责任下放到Shell层,稳定、简洁、可扩展,适用于实验平台、批量任务调度等场景。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











