如何通过PHP终止exec()启动运行的bash脚本进程?
实现方案
你当前的写法存在两个核心问题,导致无法实现中途终止:一是exec()是同步阻塞调用,PHP会一直等待脚本全量执行完成才会返回响应,前端全程处于等待状态;二是执行脚本时没有记录进程ID,也没有做进程组隔离,后续无法定位要终止的进程,也没法一次性清掉脚本启动的所有子进程(Java进程、数据库操作进程等)。
按以下步骤改造即可:
1. 改造脚本启动逻辑
不要直接把替换完参数的脚本内容拼接到bash -c参数里,特殊字符过多很容易出现转义异常。改成生成临时执行脚本,通过setsid启动独立进程组后台运行,同时记录进程ID、临时文件路径到用户会话,方便后续终止和清理。
替换display.php里的原有执行逻辑:
if( isset($_POST["submitPara"]) ){ session_start(); $runningOcc = $_POST["runningOcc"]; // 生成唯一临时脚本,避免多用户并发冲突 $tmpScriptPath = tempnam(sys_get_temp_dir(), 'task_'); $scriptContent = file_get_contents('myscript.sh'); $scriptContent = str_replace("Occur_Run", $runningOcc, $scriptContent); file_put_contents($tmpScriptPath, $scriptContent); chmod($tmpScriptPath, 0755); // 初始化日志文件存储脚本输出 $logPath = tempnam(sys_get_temp_dir(), 'log_'); // setsid启动独立进程组,后台运行,返回主进程PID $cmd = sprintf("setsid bash %s > %s 2>&1 & echo $!", $tmpScriptPath, $logPath); $pid = trim(exec($cmd)); // 把任务信息存到会话,做用户级隔离 $_SESSION['current_task'] = [ 'pid' => $pid, 'script_path' => $tmpScriptPath, 'log_path' => $logPath ]; // 直接跳转,无需等待脚本执行完成 header("Location: status.php"); exit; }
2. 新增任务终止接口
新建cancel.php文件,读取当前会话关联的任务PID,给整个进程组发送强制终止信号,清理临时文件后跳转回首页:
session_start(); if(isset($_SESSION['current_task'])){ $pid = $_SESSION['current_task']['pid']; // 注意PID前的负号:表示杀死整个进程组,连带所有子进程一起终止,不会留孤儿进程 exec(sprintf("kill -9 -%d 2>/dev/null", $pid)); // 清理临时文件 @unlink($_SESSION['current_task']['script_path']); @unlink($_SESSION['current_task']['log_path']); unset($_SESSION['current_task']); } header("Location: index.php"); exit;
3. 改造前端交互
你原本的取消按钮没有绑定任何事件,直接给它加跳转逻辑即可,修改index.php里的取消按钮代码:
<button type="button" name="Cancel" onclick="location.href='cancel.php'"> Cancel Running </button>
如果需要实时查看脚本输出,可以新建status.php做简单的日志轮询,不用等脚本全部执行完就能看到执行进度,任务执行过程中随时可以点取消按钮终止。
注意事项
- 必须使用
setsid启动进程组,杀进程时必须给PID加负号,否则脚本启动的Java、数据库相关子进程会留在后台继续运行,占用系统资源。 - 任务信息存在用户会话中,天然做了多用户隔离,不会出现误杀其他用户任务的问题。
- 保证PHP运行用户有临时目录读写权限、kill命令执行权限即可,不要给过高的系统权限。
内容的提问来源于stack exchange,提问作者sysSTD
相关产品推荐
相关产品推荐

