1.subprocess模块简介
标准库subprocess允许创建子进程,连接子进程的输入、输出、错误管道,并且获取它们的返回码。该标准库提供了run()、call()和Popen()三种不同的函数用于创建子进程,其中run()函数会阻塞当前进程,子进程结束后返回包含返回码和其他信息的CompletedProcess对象;call()函数也会阻塞当前进程,子进程结束后直接得到返回码;Popen()函数创建子进程时不阻塞当前进程,直接返回得到Popen对象,通过该对象可以对子进程进行更多的操作和控制。例如,Popen对象的kill()和terminate()方法可以用来结束该进程,send_signal()可以给子进程发送指定信号,wait()方法用来等待子进程运行结束,pid用来表示子进程的ID号,等待。
2.subprocess模块方法详解
2.1 run()方法
run()方法是在 Python 3.5 被添加的,用于运行被 arg 描述的指令。等待指令完成,然后返回一个 CompletedProcess 示例。run 方法的参数和 Popen 的构造函数一样,接受的大多数参数都被传递给该接口。(timeout, input, check 和 capture_output 除外)。
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, **other_popen_kwargs) #常用参数详解: (1)args:要执行的shell命令,默认应该是一个字符串序列,如['df', '-Th']或('df', '-Th'),也可以是一个字符串,如'df -Th',但是此时需要把shell参数的值置为True。 (2)input:input参数将被传递给Popen.communicate() 以及子进程的标准输入。 如果使用此参数,它必须是一个字节序列。 如果指定了 encoding 或 errors 或者将 text 设置为 True,那么也可以是一个字符串。 当使用此参数时,在创建内部 Popen对象时将自动带上 stdin=PIPE,并且不能再手动指定 stdin 参数。 (3)stdin、stdout 和 stderr:子进程的标准输入、输出和错误。其值可以是subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。 subprocess.PIPE表示为子进程创建新的管道,subprocess.DEVNULL表示使用os.devnull。默认使用的是 None,表示什么都不做。另外,stderr设置为STDOUT时,stderr合并到 stdout 里一起输出。 该参数是传递给Popen.communicate(),通常该参数的值必须是一个字节序列,如果universal_newlines=True,则其值应该是一个字符串。 (4)capture_output:如果 capture_output 设为 true,stdout 和 stderr 将会被捕获。在使用时,内置的 Popen对象将自动用 stdout=PIPE 和 stderr=PIPE 创建。 stdout 和 stderr 参数不应当与 capture_output 同时提供。如果你希望捕获并将两个流合并在一起,使用 stdout=PIPE 和 stderr=STDOUT 来代替 capture_output。 (5)shell:如果shell为True,那么指定的命令将通过shell执行。如果我们需要访问某些shell的特性,如管道、文件名通配符、环境变量扩展功能,这将是非常有用的。当然, python本身也提供了许多类似shell的特性的实现,如glob、fnmatch、os.walk()、os.path.expandvars()、os.expanduser()和shutil等。shell为真,unix下相当于args前面添加了"/bin/sh "" -c", windows下相当于添加"cmd.exe /c" (6)cwd: 设置工作目录 (7)timeout:timeout 参数将被传递给 Popen.communicate()。如果发生超时,子进程将被杀死并等待,TimeoutExpired 异常将在子进程中断后被抛出。 (8)check:如果check参数的值是True,且执行命令的进程以非0状态码退出,则会抛出一个CalledProcessError的异常,且该异常对象会包含参数、退出状态码、以及stdout 和stderr(如果它们有被捕获的话) (9)encoding/error/text:如果 encoding 或者 error 被指定, 或者 text 被设为 True, 标准输入, 标准输出和标准错误的文件对象将通过指定的 encoding 和 errors 以文本模式打开, 否则以默认的io.TextIOWrapper 打开。 (10)universal_newline:universal_newline参数等同于 text 并且提供了向后兼容性。 默认情况下, 文件对象是以二进制模式打开的。 (11)env:如果 env 不是 None, 它必须是一个字典, 为新的进程设置环境变量;它用于替换继承的当前进程的环境的默认行为. 它将直接被传递给 Popen。
run()方法的返回值是subprocess.CompletedProcess类的实例,表示一个进程结束了。CompletedProcess类有下面这些属性:
(1)args :启动进程的参数,通常是个列表或字符串。
(2)returncode: 进程结束状态返回码。0表示成功状态。一个负值 -N
表示子进程被信号 N
中断 (仅 POSIX).
(3)stdout :从子进程捕获到的标准输出。是一个字节序列, 或一个字符串,如果 run()是设置了 encoding, errors 或者 text=True
来运行的,则为字符串。如果未有捕获, 则为 None。
如果设置 stderr=subprocess.STDOUT
运行进程,标准输入和标准错误将被组合在这个属性中,并且 stderr 将为 None
。
(4)stderr: 捕获到的子进程的标准错误。一个字节序列, 或者一个字符串, 如果 run()是设置了参数 encoding, errors 或者 text=True
运行的,则为字符串。 如果未有捕获, 则为 None。
(5)check_returncode():如果returncode非零,抛出CalledProcessError。
2.1.1 run()方法的使用实例
(1)run()的简单使用实例
在window下运行如下代码
import subprocess completed = subprocess.run(r'dir E:2019-10-28 est',shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,encoding='gbk') print('completed:',completed) print('stderr:',completed.stderr) print('stdout:',completed.stdout) #输出结果如下: # completed: CompletedProcess(args='dir E:\2019-10-28\test', returncode=0, stdout=' 驱动器 E 中的卷是 新加卷 卷的序列号是 C4D3-3D8A E:\2019-10-28\test 的目录 2021/05/14 14:18 <DIR> . 2021/05/14 14:18 <DIR> .. 2021/03/22 15:05 85,796,736 app-release.apk 2021/04/28 12:36 21,014 appops.xml 2 个文件 85,817,750 字节 2 个目录 269,630,459,904 可用字节 ') # stderr: None # stdout: 驱动器 E 中的卷是 新加卷 # 卷的序列号是 C4D3-3D8A # # E:2019-10-28 est 的目录 # # 2021/05/14 14:18 <DIR> . # 2021/05/14 14:18 <DIR> .. # 2021/03/22 15:05 85,796,736 app-release.apk # 2021/04/28 12:36 21,014 appops.xml # 2 个文件 85,817,750 字节 # 2 个目录 269,630,459,904 可用字节
2.2 Popen类
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=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, extra_groups=None, user=None, umask=-1, encoding=None, errors=None, text=None) 参数详解 (1)args: args应当是一个程序参数的序列或者是一个单独的字符串或 path-like object。 默认情况下,如果 args 是序列则要运行的程序为 args 中的第一项。 如果 args 是字符串,则其解读依赖于具体平台 (2)bufsize: bufsize将在 open() 函数创建了 stdin/stdout/stderr 管道文件对象时作为对应的参数供应: 0 表示不使用缓冲区 (读取与写入是一个系统调用并且可以返回短内容) 1 表示行缓冲(只有 universal_newlines=True 时才有用,例如,在文本模式中) 任何其他正值表示使用一个约为对应大小的缓冲区 负的 bufsize (默认)表示使用系统默认的 io.DEFAULT_BUFFER_SIZE。 (3)executable 参数指定一个要执行的替换程序。这很少需要。当 shell=True, executable 替换 args 指定运行的程序。但是,原始的 args 仍然被传递给程序。大多数 程序将被 args 指定的程序作为命令名对待,这可以与实际运行的程序不同。在 POSIX, args 名作为实际调用程序中可执行文件的显示名称,例如 ps。如果 shell=True, 在 POSIX, executable 参数指定用于替换默认 shell /bin/sh 的 shell。 (4)stdin, stdout 和 stderr: 分别指定被运行的程序的标准输入、输出和标准错误的文件句柄。合法的值有 PIPE , DEVNULL , 一个存在的文件描述符(一个正整数), 一个存在的文件对象以及 None。 PIPE 表示应创建一个新的对子进程的管道。 DEVNULL 表示使用特殊的 os.devnull 文件。使用默认的 None,则不进行成定向;子进程的 文件流将继承自父进程。另外, stderr 可设为 STDOUT,表示应用程序的标准错误数据应和标准输出一同捕获。 (5)preexec_fn:如果 preexec_fn 被设为一个可调用对象,此对象将在子进程刚创建时被调用。(仅 POSIX) 警告 preexec_fn 形参在应用程序中存在多线程时是不安全的。子进程在调用前可能死锁。如果你必须使用它,保持警惕!最小化你调用的库的数量。 在 3.8 版更改: preexec_fn 形参在子解释器中已不再受支持。 在子解释器中使用此形参将引发 RuntimeError。 这个新限制可能会影响部署在 mod_wsgi, uWSGI 和其他 嵌入式环境中的应用。 (6)close_fds: 如果 close_fds 为真,所有文件描述符除了 0, 1, 2 之外都会在子进程执行前关闭。而当 close_fds 为假时,文件描述符遵守它们继承的标志,如文件描 述符的继承所述。 在 Windows,如果 close_fds 为真, 则子进程不会继承任何句柄,除非在 STARTUPINFO.IpAttributeList 的 handle_list 的键中显式传递,或者通过标准句柄重定向传递。 (7)pass_fds: 是一个可选的在父子进程间保持打开的文件描述符序列。提供任何 pass_fds 将强制 close_fds 为 True。(仅 POSIX) (8)cwd: 如果 cwd 不为 None,此函数在执行子进程前会将当前工作目录改为 cwd。 cwd 可以是一个字符串、字节串或路径类对象 。 特别地,当可执行文件的路径为相对路 径时,此函数会相对于*cwd* 来查找 executable (或 args 中的第一个条目)。 (9)restore_signals: 如果restore_signals 为 true(默认值),则 Python 设置为 SIG_IGN 的所有信号将在 exec 之前的子进程中恢复为 SIG_DFL。目前,这包括 SIGPIPE, SIGXFZ 和 SIGXFSZ 信号。 (仅 POSIX) (10)start_new_session: 如果 start_new_session 为 true,则 setsid() 系统调用将在子进程执行之前被执行。(仅 POSIX) (11)grop:如果 group 不为 None,则 setregid() 系统调用将于子进程执行之前在下级进程中进行。 如果所提供的值为一个字符串,将通过 grp.getgrnam() 来查找它,并将使用 gr_gid 中的值。 如果该值为一个整数,它将被原样传递。 (POSIX 专属) (12)extra_groups:如果 extra_groups 不为 None,则 setgroups() 系统调用将于子进程之前在下级进程中进行。 在 extra_groups 中提供的字符串将通过 grp.getgrnam() 来查 找,并将使用 gr_gid 中的值。 整数值将被原样传递。 (POSIX 专属) (13)user:如果 user 不为 None,则 setreuid() 系统调用将于子进程执行之前在下级进程中进行。 如果所提供的值为一个字符串,将通过 pwd.getpwnam() 来查找它,并将使用 pw_uid 中的值。 如果该值为一个整数,它将被原样传递。 (POSIX 专属) (14)umask:如果 umask 不为负值,则 umask() 系统调用将在子进程执行之前在下级进程中进行。(POSIX专属) (15)env: 如果 env 不为 None,则必须为一个为新进程定义了环境变量的字典;这些用于替换继承的当前进程环境的默认行为。 注解:如果指定, env 必须提供所有被子进程需求的变量。在 Windows,为了运行一个 side-by-side assembly ,指定的 env 必须 包含一个有效的 SystemRoot。 (16)encoding/errors/text:如果 encoding 或 errors 被指定,或者 text 为 true,则文件对象 stdin, stdout 和 stderr 将会以指定的编码和 errors 以文本模式打开,如同常用 参数所述。 universal_newlines 参数等同于 text 并且提供向后兼容性。默认情况下,文件对象都以二进制模式打开。 (17)startupinfo: 如果给出,startupinfo 将是一个将被传递给底层的 CreateProcess 函数的 STARTUPINFO 对象。
Popen类的实例拥有以下方法:
(1)Popen.poll() 检查子进程是否已被终止。设置并返回returncode属性。否则返回 None。 (2)Popen.wait(timeout=None) 等待子进程被终止。设置并返回 returncode属性。如果进程在timeout 秒后未中断,抛出一个TimeoutExpired 异常,可以安全地捕获此异常并重新等待。 注解:当stdout=PIPE或者 stderr=PIPE并且子进程产生了足以阻塞 OS 管道缓冲区接收更多数据的输出到管道时,将会发生死锁。当使用管道时用Popen.communicate() 来规避它。 (3)Popen.communicate(input=None,timeout=None) 与进程交互:将数据发送到 stdin。 从 stdout 和 stderr 读取数据,直到抵达文件结尾。 等待进程终止并设置retuencode属性。 可选的 input 参数应为要发送到下级进程的数据,或者如果没有要发送到下级进程的数据则为None。 如果流是以文本模式打开的则 input 必须为字符串。 在其他情况下,它必须为字节串。 communicate()返回一个(stdout_data,stderr_data) 元组。如果文件以文本模式打开则为字符串;否则字节。 注意如果你想要向进程的 stdin 传输数据,你需要通过stdin=PIPE创建此 Popen 对象。类似的,要从结果元组获取任何非 None 值,你同样需要设置 stdout=PIPE或者 stderr=PIPE。 如果进程在 timeout 秒后未终止,一个TimeoutExpired异常将被抛出。捕获此异常并重新等待将不会丢失任何输出。 如果超时到期,子进程不会被杀死,所以为了正确清理一个行为良好的应用程序应该杀死子进程并完成通讯。 proc = subprocess.Popen(...) try: outs, errs = proc.communicate(timeout=15) except TimeoutExpired: proc.kill() outs, errs = proc.communicate() 注解:内存里数据读取是缓冲的,所以如果数据尺寸过大或无限,不要使用此方法。 (4)Popen.send_signal(signal) 将信号 signal 发送给子进程。如果进程已完成则不做任何操作。 注解: 在 Windows, SIGTERM 是一个terminate() 的别名。 CTRL_C_EVENT 和 CTRL_BREAK_EVENT 可以被发送给以包含CREATE_NEW_PROCESS的creationflags形参启动的进程。 (5)Popen.terminate() 停止子进程。 在 POSIX 操作系统上,此方法会发送 SIGTERM 给子进程。 在 Windows 上则会调用 Win32 API 函数TerminateProcess()来停止子进程。 (6)Popen.kill() 杀死子进程。 在 POSIX 操作系统上,此函数会发送 SIGKILL 给子进程。 在 Windows 上kill()则是 terminate()的别名。
以下属性也是可用的:
(1)Popen.args args 参数传递给Popen, 一个程序参数的序列或者一个简单字符串。 (2)Popen.stdin 如果 stdin 参数为PIPE,此属性是一个类似 open()返回的可写的流对象。如果 encoding 或 errors 参数被指定或者 universal_newlines 参数为True,则此流是一个文本流,否则是字节流。如果 stdin 参数非PIPE, 此属性为 None。 (3)Popen.stdout 如果 stdout 参数是PIPE,此属性是一个类似open()返回的可读流。从流中读取子进程提供的输出。如果 encoding 或 errors 参数被指定或者 universal_newlines 参数为 True,此流为文本流,否则为字节流。如果 stdout 参数非PIPE,此属性为 None。 (4)Popen.stderr 如果 stderr 参数是 PIPE,此属性是一个类似 Open() 返回的可读流。从流中读取子进程提供的输出。如果 encoding 或 errors 参数被指定或者 universal_newlines 参数为 True,此流为文本流,否则为字节流。如果 stderr 参数非 PIPE,此属性为 None。 警告:使用 communicate()而非 .stdin.write,.stdout.read 或者.stderr.read来避免由于任意其他 OS 管道缓冲区被子进程填满阻塞而导致的死锁。 (5)Popen.pid 子进程的进程号。 注意如果你设置了 shell 参数为 True,则这是生成的子 shell 的进程号。 (6)Popen.returncode 此进程的退出码,由poll()和 wait() 设置(以及直接由communicate()设置)。一个 None值表示此进程仍未结束。 一个负值-N表示子进程被信号 N中断 (仅 POSIX).
应用实例:
import subprocess image_name = r'E:work5-2 estCAM00.png' output_path = r'E:work5-2 est' cmd = r"E:3d180inTestIntrinsicCalibration.exe -mode fisheye_optical_center -fisheye_optical_center_src_image {0} -fisheye_optical_center_threshold 220 -vis_output_dir {1}" cmd = cmd.format(image_name, output_path) current_process = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.STDOUT) # lines = [] # while current_process.poll()==None: # line = current_process.stdout.readline() # lines.append(line) # print(lines) standoutput,standerr = current_process.communicate() current_process.wait() current_process.kill() print(standoutput)
使用该模块常见坑:
需求:脚本 A 需调用一个外部程序 B 来做一些工作,并获取 B 的 stdout 输出 法1: import subprocess child = subprocess.Popen(['./B'], stdout=subprocess.PIPE) child.wait() print(child.stdout) 这种方式官方文档中再三警告,会导致死锁问题,导致死锁的原因:管道的大小是有所限制的,当子进程一直向 stdout 管道写数据且写满的时候,子进程将发生 I/O 阻塞; 而此时父进程只是干等子进程退出,也处于阻塞状态; 法2: import subprocess child = subprocess.Popen(['./B'], stdout=subprocess.PIPE) stdout, stderr = child.communicate() 这是官方推荐的方式,但是也有坑:communicate 其实是循环读取管道中的数据(每次32768字节(32KB))并将其存在一个 list 里面,到最后将 list 中的所有数据连接起来 (b''.join(list)) 返回给用户。于是就出现了一个坑:如果子进程输出内容非常多甚至无限输出,则机器内存会被撑爆。 法3: outputfile = r'E:work5-2 est.txt' with open(outputfile,mode='a+') as f: current_process = subprocess.Popen(cmd,stdout=f) current_process.wait() 第三种的思路其实很简单,就是当子进程的可能输出非常大的时候,直接将 stdout 或 stderr 重定向到文件,避免了撑爆内存的问题。不过这种情形很少见,不常用
>>>待续