python subprocess模块

一、subprocess 以及常用的封装函数
运行 python 的时候,我们都是在创建并运行一个进程。像 Linux 进程那样,一个进程可以 fork 一个子进程,并让这个子进程 exec 另外一个程序。在 Python 中,我们通过标准库中的 subprocess 包来 fork 一个子进程,并运行一个外部的程序。
subprocess 包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。另外 subprocess 还提供了一些管理标准流 (standard stream) 和管道 (pipe) 的工具,从而在进程间使用文本通信。

subprocess.call()
父进程等待子进程完成
返回退出信息 (returncode,相当于 Linux exit code)

subprocess.check_call()
父进程等待子进程完成
返回 0
检查退出信息,如果 returncode 不为 0,则举出错误 subprocess.CalledProcessError,该对象包含有 returncode 属性,可用 try…except…来检查

subprocess.check_output()
父进程等待子进程完成
返回子进程向标准输出的输出结果
检查退出信息,如果 returncode 不为 0,则举出错误 subprocess.CalledProcessError,该对象包含有 returncode 属性和 output 属性,output 属性为标准输出的输出结果,可用 try…except…来检查。

这三个函数的使用方法相类似,下面来以 subprocess.call() 举例说明:

代码如下:

>>> import subprocess
>>> retcode = subprocess.call(["ls", "-l"])
#和 shell 中命令 ls -a 显示结果一样
>>> print retcode
0


将程序名 (ls) 和所带的参数 (-l) 一起放在一个表中传递给 subprocess.call()

 

shell 默认为 False,在 Linux 下,shell=False 时, Popen 调用 os.execvp() 执行 args 指定的程序;shell=True 时,如果 args 是字符串,Popen 直接调用系统的 Shell 来执行 args 指定的程序,如果 args 是一个序列,则 args 的第一项是定义程序命令字符串,其它项是调用系统 Shell 时的附加参数。

上面例子也可以写成如下:

代码如下:
>>> retcode = subprocess.call("ls -l",shell=True)


在 Windows 下,不论 shell 的值如何,Popen 调用 CreateProcess()执行 args 指定的外部程序。如果 args 是一个序列,则先用 list2cmdline() 转化为字符串,但需要注意的是,并不是 MS Windows 下所有的程序都可以用 list2cmdline 来转化为命令行字符串。

 

subprocess.Popen()

代码如下:

class Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)


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

 

与上面的封装不同,Popen 对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的 wait()方法,父进程才会等待 ( 也就是阻塞 block),举例:

代码如下:
>>> import subprocess
>>> child = subprocess.Popen(['ping','-c','4','blog.linuxeye.com'])
>>> print 'parent process'


从运行结果中看到,父进程在开启子进程之后并没有等待 child 的完成,而是直接运行 print。

 

对比等待的情况:

代码如下:
>>> import subprocess
>>> child = subprocess.Popen('ping -c4 blog.linuxeye.com',shell=True)
>>> child.wait()
>>> print 'parent process'


从运行结果中看到,父进程在开启子进程之后并等待 child 的完成后,再运行 print。
此外,你还可以在父进程中对子进程进行其它操作,比如我们上面例子中的 child 对象: 代码如下:


child.poll() # 检查子进程状态
child.kill() # 终止子进程
child.send_signal() # 向子进程发送信号
child.terminate() # 终止子进程


子进程的 PID 存储在 child.pid
二、子进程的文本流控制
子进程的标准输入、标准输出和标准错误如下属性分别表示:

代码如下:

child.stdin
child.stdout
child.stderr


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

代码如下:
>>> import subprocess
>>> child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
>>> print child1.stdout.read(),
#或者 child1.communicate()
>>> import subprocess
>>> child1 = subprocess.Popen(["cat","/etc/passwd"], stdout=subprocess.PIPE)
>>> child2 = subprocess.Popen(["grep","0:0"],stdin=child1.stdout, stdout=subprocess.PIPE)
>>> out = child2.communicate()


subprocess.PIPE 实际上为文本流提供一个缓存区。child1 的 stdout 将文本输出到缓存区,随后 child2 的 stdin 从该 PIPE 中将文本读取走。child2 的输出文本也被存放在 PIPE 中,直到 communicate() 方法从 PIPE 中读取出 PIPE 中的文本。
注意:communicate() 是 Popen 对象的一个方法,该方法会阻塞父进程,直到子进程完成

 

 

 

subprocess 模块首先推荐使用的是它的 run 方法,更高级的用法可以直接使用 Popen 接口。

run 方法语法格式如下:

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)
  • args:表示要执行的命令。必须是一个字符串,字符串参数列表。
  • stdin、stdout 和 stderr:子进程的标准输入、输出和错误。其值可以是 subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。subprocess.PIPE 表示为子进程创建新的管道。subprocess.DEVNULL 表示使用 os.devnull。默认使用的是 None,表示什么都不做。另外,stderr 可以合并到 stdout 里一起输出。
  • timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出 TimeoutExpired 异常。
  • check:如果该参数设置为 True,并且进程退出状态码不是 0,则弹 出 CalledProcessError 异常。
  • encoding: 如果指定了该参数,则 stdin、stdout 和 stderr 可以接收字符串数据,并以该编码方式编码。否则只接收 bytes 类型的数据。
  • shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。

run 方法调用方式返回 CompletedProcess 实例,和直接 Popen 差不多,实现是一样的,实际也是调用 Popen,与 Popen 构造函数大致相同,例如:

实例

#执行 ls -l /dev/null 命令
>>> subprocess.run(["ls", "-l", "/dev/null"])
crw-rw-rw-  1 root  wheel    3,   2  5  4 13:34 /dev/null
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0)

returncode: 执行完子进程状态,通常返回状态为 0 则表明它已经运行完毕,若值为负值 "-N", 表明子进程被终。

简单实例:

实例

import subprocess
def runcmd(command):
    ret = subprocess.run(command,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8",timeout=1)
    if ret.returncode == 0:
        print("success:",ret)
    else:
        print("error:",ret)
runcmd(["dir","/b"])# 序列参数
runcmd("exit 1")# 字符串参数

输出结果如下:

success: CompletedProcess(args=['dir', '/b'], returncode=0, stdout='test.py\n', stderr='')
error: CompletedProcess(args='exit 1', returncode=1, stdout='', stderr='')

Popen() 方法

Popen 是 subprocess 的核心,子进程的创建和管理都靠它处理。

构造函数:

class subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, 
preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, 
startupinfo=None, creationflags=0,restore_signals=True, start_new_session=False, pass_fds=(),
*, encoding=None, errors=None)

常用参数:

  • args:shell 命令,可以是字符串或者序列类型(如:list,元组)
  • bufsize:缓冲区大小。当创建标准流的管道对象时使用,默认 -1。
    0:不使用缓冲区
    1:表示行缓冲,仅当 universal_newlines=True 时可用,也就是文本模式
    正数:表示缓冲区大小
    负数:表示使用系统默认的缓冲区大小。
  • stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
  • preexec_fn:只在 Unix 平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
  • shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
  • cwd:用于设置子进程的当前目录。
  • env:用于指定子进程的环境变量。如果 env = None,子进程的环境变量将从父进程中继承。

创建一个子进程,然后执行一个简单的命令:

实例

>>> import subprocess
>>> p = subprocess.Popen('ls -l', shell=True)
>>> total 164
-rw-r--r--  1 root root   133 Jul  4 16:25 admin-openrc.sh
-rw-r--r--  1 root root   268 Jul 10 15:55 admin-openrc-v3.sh
...
>>> p.returncode
>>> p.wait()
0
>>> p.returncode

这里也可以使用 p = subprocess.Popen(['ls', '-cl']) 来创建子进程。

Popen 对象方法

  • poll(): 检查进程是否终止,如果终止返回 returncode,否则返回 None。
  • wait(timeout): 等待子进程终止。
  • communicate(input,timeout): 和子进程交互,发送和读取数据。
  • send_signal(singnal): 发送信号到子进程 。
  • terminate(): 停止子进程, 也就是发送 SIGTERM 信号到子进程。
  • kill(): 杀死子进程。发送 SIGKILL 信号到子进程。

实例

import time
import subprocess
def cmd(command):
    subp = subprocess.Popen(command,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8")
    subp.wait(2)
    if subp.poll() == 0:
        print(subp.communicate()[1])
    else:
        print("失败")
cmd("java -version")
cmd("exit 1")

输出结果如下:

java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)

失败