Python 子进程管理–subprocess

王 茂南 2020年12月19日07:07:33
评论
3674字阅读12分14秒
摘要这一篇文章介绍 Python 的一个标准库,subprocess,使用他可以对 python 的子进程进行管理,也可以在 python 脚本中运行一些外部的程序。

简介

这一篇主要介绍 Python 标准库 subprocess 的使用。subprocess 包主要功能是执行外部的命令和程序。比如说,我需要使用 wget 下载文件。我在 Python 中调用 wget 程序。从这个意义上来说,subprocess 的功能与 shell 类似。

通过使用 subprocess 包,我们可以运行外部程序。这极大的拓展了 Python 的功能。如果你已经了解了操作系统的某些应用,你可以从 Python 中直接调用该应用 (而不是完全依赖 Python ),并将应用的结果输出给 Python,并让 Python 继续处理。(摘自,Python标准库06 子进程 (subprocess包)

参考资料

 

subprocess 即常见的封装函数

当我们允许 python 程序的时候,我们是创建了一个进程。一个进程可以 fork 一个子进程,这个子进程可以运行另外一个程序。在 python 中,我们可以使用标准库 subprocess 来fork 一个子进程,并运行外部程序。

在使用 subprocess 创建子进程的时候,有以下几个点需要注意:

  1. 当创建子进程之后,父进程是否暂停,等待子进程运行;
  2. 子进程返回是什么;
  3. 当 returncode 不是 0 的时候,父进程应该如何处理;

 

subprocess.call

subprocess.call 是父进程等待子进程完成,同时返回 return code。下面看一个简单的例子。

  1. import subprocess
  2. rc = subprocess.call(["dir"], shell=True)

我们使用了 shell=True 这个参数。这个时候,Python 将先运行一个 shell,再用这个 shell 来解释这整个字符串。我们在 windows 下运行一些 exe 的时候,也需要使用 shell=True 的方式来进行运行。

WindowsLinux 下,使用 shell=True 这个设置得到的效果是不同的。例如运行下面的代码,在 Windows 上可以正确显示:

  1. import subprocess
  2. child = subprocess.Popen(["ping",
  3.                          "www.baidu.com"],
  4.                         stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
  5. std_out, err_out = child.communicate()
  6. child.wait() # 等待子进程完成
  7. print(std_out.decode('gb2312'), err_out)

但是上面同样的代码,在 Linux 中会出现「ping: usage error: Destination address required」的错误。这是因为设置了 shell=True 之后,在 Linux 中相当于运行了 /bin/sh -c "ping" "baidu.com",而不是我们想要的 /bin/sh -c "ping baidu.com"。此时 ping 没有获得参数 baidu.com

Python 子进程管理–subprocess

 

subprocess.Popen

事实上,上面的 subprocess.call 就是基于 subprocess.Popen 的封装。这些封装的目的在于让我们容易使用子进程。当我们想要更个性化我们的需求的时候,就要转向Popen类,该类生成的对象用来代表子进程。

与上面的封装不同,Popen 对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的wait() 方法,父进程才会等待。可以看下面的例子,

  1. import subprocess
  2. child = subprocess.Popen(["ping", "www.baidu.com"])
  3. print("parent process")

父进程在开启子进程之后,没有等待,而是先进行 print 之后,才开始有子进程的信息。

Python 子进程管理–subprocess

我们需要使用 child.wait() 来是的父进程等待子进程的完成。

  1. import subprocess
  2. child = subprocess.Popen(["ping", "www.baidu.com"])
  3. child.wait()
  4. print("parent process")

可以看到此时父进程是等子进程运行完毕之后才开始运行的。

Python 子进程管理–subprocess

 

子进程的输入和输出异常控制

在使用 Popen 的时候,我们可以分别控制子进程的标准输入和输出,和子进程的异常。这些分别是:

  • child.stdin,标准输入
  • child.stdout,标准输出
  • child.stderr,异常

我们可以在 Popen() 建立子进程的时候改变标准输入、标准输出和标准错误,并可以利用subprocess.PIPE 将多个子进程的输入和输出连接在一起,构成管道 (pipe) :

  1. import subprocess
  2. child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
  3. child2 = subprocess.Popen(["wc"], stdin=child1.stdout,stdout=subprocess.PIPE)
  4. out = child2.communicate()
  5. print(out.decode('gb2312'))

在上面的例子中,subprocess.PIPE 实际上为文本流提供一个缓存区。child1 的 stdout 将文本输出到缓存区,随后 child2 的 stdin 从该 PIPE 中将文本读取走。child2 的输出文本也被存放在 PIPE 中,直到 communicate() 方法从 PIPE 中读取出 PIPE 中的文本。

其中,communicate() 是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成。同时要注意,在 windows 下输出的编码,可能需要使用 decode('gb2312') 来进行解码才可以正常显示。

我们可以借助其来运行一些 windows 下的 exe 的程序,例如下面运行 editcap.exe 来进行流量的分割,可以在其中指定参数,具体的可以参考下面的写法。

  1. prog = subprocess.Popen(["editcap.exe",
  2.                 "-F", "libpcap",
  3.                 "-T", "ether",
  4.                 '1.pcapng',
  5.                 '1.pcap'],
  6.             stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
  7. _, _ = prog.communicate()

 

将子进程输出到临时文件

在Python中,调用 subprocess.Popen的时候,如果调用的 shell 命令本身在执行之后会突然出现很多输出,则这个时候可能会导致 hang 在那里,表现就是卡死了,程序也不往下走,也不会报错。

这个原因就是,PIPE 本身可容纳的量比较小,所以程序会卡死,所以一大堆内容输出过来的时候,会导致 PIPE 不足够处理这些内容,因此需要将输出内容定位到其他地方,例如临时文件等。

下面我们尝试将 subprocess.Popen() 的内容重定向到临时文件中。例如下面是不重定向到临时文件的写法:

  1. import subprocess
  2. child = subprocess.Popen(["ping", "www.baidu.com",
  3.                          "-c", "10"],
  4.                         stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
  5. std_out, err_out = child.communicate()
  6. child.wait() # 等待子进程完成
  7. print(std_out, err_out)

下面我们将输出和 err 都重定向到文件中去,这里就不需要使用 communicate 了:

  1. import subprocess
  2. from tempfile import TemporaryFile
  3. temp_file = TemporaryFile()
  4. child = subprocess.Popen(["ping", "www.baidu.com",
  5.                          "-c", "10"],
  6.                         stdout=temp_file, stderr=temp_file, shell=False)
  7. child.wait() # 等待子进程完成
  8. temp_file.seek(0)
  9. print(temp_file.readlines())

 

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南
  • 本文由 发表于 2020年12月19日07:07:33
  • 转载请务必保留本文链接:https://mathpretty.com/13242.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: