You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python3中如何可靠获取sudo启动的目标进程PID以实现进程终止

如何可靠获取sudo启动的底层进程PID?

这个问题我之前也碰到过——当用subprocess.Popen执行带sudo的命令时,返回的PID确实是sudo进程本身,而不是你真正想控制的目标进程。直接调用terminate()或者kill()只会杀掉sudo进程,目标进程会被系统init进程接管继续运行;但如果Python进程退出,sudo进程也会跟着退出,目标进程就会终止,这是因为sudo默认会在父进程退出时清理自己的子进程,但手动发送信号给sudo时,它不会主动把信号转发给子进程。

下面给你几个可靠的解决方案,按推荐程度排序:

方案1:用psutil遍历进程树(最推荐)

psutil是Python生态里非常好用的进程管理库,它可以轻松获取进程的父子关系,完美解决这个问题——我们只需要找到你启动的sudo进程的直接子进程,那就是目标进程。

首先安装psutil:

pip install psutil

然后修改你的测试代码:

import subprocess
import time
import psutil
import signal

cmd = ["sudo", "testscript.sh"]

def get_target_pid(sudo_pid):
    try:
        # 获取sudo进程对象
        sudo_process = psutil.Process(sudo_pid)
        # 只获取直接子进程(避免递归拿到更深的子进程)
        children = sudo_process.children(recursive=False)
        if children:
            return children[0].pid
        return None
    except psutil.NoSuchProcess:
        # 说明sudo进程已经退出了
        return None

def myfunction(action, process=None):
    if action == "start":
        process = subprocess.Popen(cmd)
        # 给一点时间让目标进程启动(避免sudo还没拉起子进程就去查)
        time.sleep(0.1)
        return process
    if action == "stop":
        target_pid = get_target_pid(process.pid)
        if target_pid:
            try:
                # 先尝试优雅终止(SIGTERM)
                target_process = psutil.Process(target_pid)
                target_process.terminate()
                # 等待0.5秒看是否正常终止,不行就强制杀(SIGKILL)
                time.sleep(0.5)
                if target_process.is_running():
                    target_process.kill()
                print(f"目标进程 {target_pid} 已成功终止")
            except psutil.NoSuchProcess:
                print("目标进程已经不存在")
        # 最后杀掉sudo进程本身
        process.terminate()

process = myfunction("start")
time.sleep(5)
myfunction("stop", process)

这个方案的优势在于:完全绑定到你启动的那个sudo进程,不会误杀其他同名进程,而且是纯Python实现,跨平台性好,不需要依赖外部命令。

方案2:用唯一环境变量标记目标进程

如果不想引入psutil依赖,也可以通过给目标进程设置唯一环境变量的方式来定位它。我们在启动时给目标进程加一个独一无二的标记,之后通过这个标记查找PID:

import subprocess
import time
import os
import signal

# 生成一个唯一标记:当前Python进程PID + 时间戳,确保不会重复
unique_tag = f"MY_UNIQUE_PROCESS_MARK={os.getpid()}_{int(time.time())}"
# 用sudo -E保留环境变量,然后通过bash -c传递标记给目标脚本
cmd = ["sudo", "-E", "bash", "-c", f"{unique_tag} ./testscript.sh"]

def get_target_pid():
    # 用pgrep查找包含唯一标记的进程
    result = subprocess.run(
        ["pgrep", "-f", unique_tag],
        capture_output=True,
        text=True
    )
    if result.returncode == 0:
        # 取出所有匹配的PID,取最后一个(最新启动的)
        pids = result.stdout.strip().split('\n')
        return int(pids[-1]) if pids else None
    return None

def myfunction(action, process=None):
    if action == "start":
        process = subprocess.Popen(cmd)
        time.sleep(0.1)
        return process
    if action == "stop":
        target_pid = get_target_pid()
        if target_pid:
            try:
                os.kill(target_pid, signal.SIGTERM)
                time.sleep(0.5)
                if os.path.exists(f"/proc/{target_pid}"):
                    os.kill(target_pid, signal.SIGKILL)
                print(f"目标进程 {target_pid} 已终止")
            except OSError:
                print("目标进程已经不存在")
        process.terminate()

process = myfunction("start")
time.sleep(5)
myfunction("stop", process)

这个方法依赖pgrep命令(Linux/macOS默认都有),唯一要注意的是标记要足够唯一,避免和其他进程的命令行参数冲突。

方案3:让sudo启动后立即退出(不推荐)

如果你的目标进程启动后不需要再持有sudo权限,可以用sudo -b让sudo启动进程后后台运行并退出,这样subprocess拿到的sudo进程会很快结束,目标进程变成孤儿进程。但这种方式你还是需要额外手段追踪目标PID,而且如果目标进程启动失败,你很难感知到,所以一般不推荐。


内容的提问来源于stack exchange,提问作者patman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 19:57:47