Python subprocess使用shell=True:重定向和跨平台进程终止

6 投票
4 回答
6147 浏览
提问于 2025-04-16 05:20

我在使用Python的subprocess模块时遇到了一些困难(据我所知,这个模块应该是一个统一的、跨平台的抽象工具,但我不想多说这个 :))。

我想做的简单事情如下:

  • 启动一个外部应用程序(可能用subprocess),并使用类似于shell的重定向(比如 './myapp >stdout_log >stderr_log')。
    • 基本上,我想执行一个shell命令,所以我必须在subprocess.Popen()中指定shell=True(否则命令行中的重定向就不起作用)。
  • 我希望以异步的方式启动这个命令行(这样它就会作为一个独立的子进程运行,而我的Python进程不会等待它完成)。
  • (我的父Python进程会不时查看子进程的日志以提取信息,但这和问题无关。)
  • 如果我的父Python进程决定,它应该能够终止这个子进程。

现在,我面临的主要问题是:

  • 我基本上被迫使用shell=True,才能让重定向正常工作。
  • 在父Python进程中处理子进程的stdout/stderr不是一个选项,因为我找不到在不等待的情况下处理它的功能(而且父Python进程在子进程运行时必须做其他事情)。
  • 如果我使用shell=True,那么subprocess.kill()只会终止shell,而不会终止子进程。
  • 我需要一种可靠的子进程终止方法,能够在任何平台上工作(至少是Linux和Windows)。

我希望我说得够具体。提前感谢任何建议/提示——我刚花了一整天在subprocess上,个人觉得它远没有实现跨平台或简单 :((但也许只是我自己觉得这样)。

更新(2010-10-13):

如果你启动一个子进程(即使是使用shell=False),那么subprocess.Popen.kill()函数只会杀掉那个子进程(所以如果有任何“孙子”进程,它们不会被终止)。

我读到可以使用preexec_fn参数来设置所有子进程的sid,但这仅适用于Unix:超时一个子进程

4 个回答

3

针对一些问题的解答:

4

我最近也遇到了类似的情况。我创建了一个使用 shell=True 的 Python 子进程,后来需要把它杀掉。不过,由于使用了这个 shell 参数,真正的进程其实是 shell 的子进程,也就是主进程的孙子进程。杀掉子 shell 并不会自动杀掉它的孙子进程。

在我的顶层 Python 脚本中:

childProcess = subprocess.Popen('python other.py', shell=True)

为了同时杀掉 shell 和那个“真正工作”的孙子进程,我是这么做的:

subprocess.call("ps -ef | awk '$3 == \"" + str(childProcess.pid) + "\" {print $2}' | xargs kill -9", shell=True)
childProcess.kill()

第一行代码是杀掉所有子进程(根据 ps -ef 的父子关系),第二行代码则是杀掉子进程。

有趣的是,这在 Ubuntu 上是必要的,但在 Mac OSX 上并不是绝对必要,因为在 Mac 上,孙子进程似乎会接管原来的子 shell 进程的 ID。

4

上次我遇到类似的情况时,发现最简单(几乎是唯一)的解决办法就是启动一个线程来处理你的子进程。用这种方法你可以有不同的选择,比如解析类似命令行的管道,然后在Python代码中执行这些命令(不过你说这样不行,因为会阻塞),这样也能解决你遇到的杀死进程的问题。总的来说,使用线程封装似乎是个不错的选择。

可惜我对subprocess的经验都是在Windows平台上,Windows有很多自己独特的小毛病。subprocess似乎有很多缺陷,不过考虑到它有popenpopen2等模块来替代,应该也算是能勉强用吧。

撰写回答