🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 概述 subprocess模块中只定义了一个类:Popen。可以使用Popen来创建进程,并与进程进行复杂的交互。它的构造函数如下。 --- ### Popen使用示例 示例1,使用shlex.split先对参数进行处理 ``` >>> import shlex, subprocess >>> command_line = input() /bin/vikings -input eggs.txt -output "spam spam.txt" -cmd "echo '$MONEY'" >>> args = shlex.split(command_line) >>> print(args) ['/bin/vikings', '-input', 'eggs.txt', '-output', 'spam spam.txt', '-cmd', "echo '$MONEY'"] >>> p = subprocess.Popen(args) # Success! ``` 示例2. 直接使用列表作为参数 ``` Popen(['/bin/sh', '-c', args[0], args[1], ...]) ``` 示例3. 这个上下文管理器在python3中才可以使用 ``` with Popen(["ifconfig"], stdout=PIPE) as proc: log.write(proc.stdout.read()) ``` --- ### subprocess.Popen参数 ``` subprocess.Popen(args, bufsize=0, executable=None, shell=False, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False,cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0) ``` `参数args`:**字符串**或者**序列类型**(如:list,元组),用于指定进程的可执行文件及其参数。如果是序列类型,第一个元素通常是可执行文件的路径。 `参数bufsize`:指定缓冲。 `参数executable`: 用于指定可执行程序。一般情况下我们通过args参数来设置所要运行的程序。如果将参数shell设为True,executable将指定程序使用的shell。 `参数stdin, stdout, stderr`: 分别表示程序的标准输入、输出、错误句柄。他们可以是PIPE,文件描述符或文件对象,也可以设置为None,表示`从父进程继承`。 `参数preexec_fn`: 只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用。 `参数shell`: 设为true,程序将通过shell来执行。 `参数cwd`: 用于设置子进程的当前目录。 `参数env`: 是字典类型,用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。 `参数Universal_newlines`: 不同操作系统下,文本的换行符是不一样的。如:windows下用'\r\n'表示换,而Linux下用'\n'。如果将此参数设置为True,Python统一把这些换行符当作'\n'来处理。 --- ### subprocess.Popen的方法 * Popen.poll() 用于检查子进程是否已经结束。设置并返回returncode属性。 * Popen.wait() 等待子进程结束。设置并返回returncode属性。 * Popen.communicate(input=None) 与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。Communicate()返回一个元组:`(stdoutdata, stderrdata)`。注意:如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。 * Popen.send_signal(signal) 向子进程发送信号。 * Popen.terminate() 停止(stop)子进程。在windows平台下,该方法将调用Windows API TerminateProcess()来结束子进程。 * Popen.kill() 杀死子进程。 * Popen.stdin 如果在创建Popen对象是,参数stdin被设置为PIPE,Popen.stdin将返回一个文件对象用于策子进程发送指令。否则返回None。 * Popen.stdout 如果在创建Popen对象是,参数stdout被设置为PIPE,Popen.stdout将返回一个文件对象用于策子进程发送指令。否则返回None。 * Popen.stderr 如果在创建Popen对象是,参数stdout被设置为PIPE,Popen.stdout将返回一个文件对象用于策子进程发送指令。否则返回None。 * Popen.pid 获取子进程的进程ID。 * Popen.returncode 获取进程的返回值。如果进程还没有结束,返回None。 下面是一个非常简单的例子,来演示supprocess模块如何与一个控件台应用程序进行交互。 --- ### supprocess其他 * subprocess.PIPE 在创建Popen对象时,subprocess.PIPE可以作为初始化stdin,stdout或stderr参数。表示与子进程通信的标准流。 * subprocess.STDOUT 创建Popen对象时,用于初始化stderr参数,表示将错误通过标准输出流输出。 * subprocess.call(\*popenargs, \*\*kwargs) 运行命令。该函数将一直等待到子进程运行结束,并返回进程的`returncode`。如果子进程不需要进行交互,就可以使用该函数来创建。 > 报错是shell错误,无法捕捉 * subprocess.check_call(\*popenargs, \*\*kwargs) 与subprocess.call(\*popenargs, \*\*kwargs)功能一样,只是如果子进程返回的returncode不为0的话,将触发CalledProcessError异常。在异常对象中,包括进程的`returncode`信息。 > 报错是python错误,在python中可以捕捉 ### 设置超时时间 通过改写,设置脚本执行的超时时间 ~~~ #!/usr/bin/env python # coding=utf-8 import shlex import datetime import subprocess import time def execute_command(cmdstring, cwd=None, timeout=None, shell=False): """执行一个SHELL命令封装了subprocess的Popen方法, 支持超时判断,支持读取stdout和stderr 参数: cwd: 运行命令时更改路径,如果被设定,子进程会直接先更改当前路径到cwd timeout: 超时时间,秒,支持小数,精度0.1秒 shell: 是否通过shell运行 Returns: return_code Raises: Exception: 执行超时 """ if shell: cmdstring_list = cmdstring else: cmdstring_list = shlex.split(cmdstring) if timeout: end_time = datetime.datetime.now() + datetime.timedelta(seconds=timeout) #没有指定标准输出和错误输出的管道,因此会打印到屏幕上; sub = subprocess.Popen(cmdstring_list, cwd=cwd, stdin=subprocess.PIPE,shell=shell,bufsize=4096) #subprocess.poll()方法:检查子进程是否结束了,如果结束了,设定并返回码,放在subprocess.returncode变量中 while sub.poll() is None: time.sleep(0.1) if timeout: if end_time <= datetime.datetime.now(): raise Exception("Timeout:%s"%cmdstring) return str(sub.returncode) if __name__=="__main__": print execute_command("sleep 5", timeout=2) ~~~ --- ### Popen的wait方法和communicate方法比较 Popen的wait方法之后程序一直没有返回,原因是wait是有可能产生死锁。 使用 subprocess 模块的 Popen 调用外部程序,如果 stdout 或 stderr 参数是 PIPE,并且程序输出超过操作系统的 pipe size时,如果使用 Popen.wait() 方式等待程序结束获取返回值,会导致死锁,程序卡在 wait() 调用上。 `ulimit -a` 看到的 pipe size 是 4KB,那只是每页的大小,查询得知 Linux 默认的 pipe size 是 `64KB`。 #### 对wait方法和communicate方法进行测试 ``` #!/usr/bin/env python # coding: utf-8 import subprocess def test(size): print 'start' cmd = 'dd if=/dev/urandom bs=1 count=%d 2>/dev/null' % size p = subprocess.Popen(args=cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) p.communicate() #p.wait() print 'end' ``` 测试结果 p.wait工作时,64kb时工作正常,64k+1时,程序卡住,产生死锁。 p.communicate工作时,两种情况下都可以正常工作。 ``` # 64KB test(64 * 1024) # 64KB + 1B test(64 * 1024 + 1) ``` 官方文档里推荐使用 `Popen.communicate()`。这个方法会把输出放在`内存`,而不是管道里,所以这时候上限就和内存大小有关了。而且如果要获得程序返回值,可以在调用 Popen.communicate() 之后取 Popen.returncode 的值。 结论 如果使用 subprocess.Popen,应该使用 Popen.communicate() 来等待外部程序执行结束。 --- ### subprocess的两种方法 阻塞和非阻塞 1)如果想调用之后直接阻塞到子程序调用结束 ``` #start and block until done import shlex, subprocess cmd = 'dd if=/dev/zero of=bigfile bs=1M count=32' cmd = shlex.split(cmd) subprocess.call(cmd) ``` 2)非阻塞的时候方式 ``` #start and process things, then wait from subprocess import Popen,PIPE cmd = 'dd if=/dev/zero of=bigfile bs=1M count=32' p = Popen(args=cmd, shell=True, stdout=PIPE,stderr=PIPE) print p.communicate() print 'end...' ```