subprocess模块使用

subprocess — Subprocess management

subprocess允许你生成新的processes,并连接到这些processes的input/output/error pipes,从而获取返回值

subprocess.run(…)

生成一个新的进程,运行args描述的命令,等待命令完成后返回CompletedProcess实例,从该实例中可以获取新进程的运行结果。
subprocess.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: 表示要执行的命令,必须是一个字符串或字符串列表
subprocess.run(["ls", "-l", "/dev/null"]) 
  • stdin, stdout and stderr:

子进程的标准输入、输出和错误。其值可以是 subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。

subprocess.PIPE: 子进程打开通向标准流的管道,创建一个新的管道文件
subprocess.DEVNULL 表示使用 os.devnull。默认使用的是 None,表示什么都不做。
subprocess.STDOUT: 用于初始化stderr,表示将错误通过stdout输出.(stderr 可以合并到 stdout 里一起输出:stderr=subprocess.STDOUT)

stdout指定后,命令输出结果保存到到CompletedProcess.stdout属性:

In [7]: ret = subprocess.run(["ls"],shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,encoding="utf-8",timeout=1)   

In [8]: ret                                                                                                                  
Out[8]: CompletedProcess(args=['ls'], returncode=0, stdout='build\ncs\ndist\nfifo\nHello.egg-info\nhello.py\n__init__.py\n__pycache__\nsetup.py\nsimple_markup.py\nstt\ntest_input.txt\ntest_output.html\ntest.py\nutil.py\n')

In [9]: ret.stdout                                                                                                           
Out[9]: 'build\ncs\ndist\nfifo\nHello.egg-info\nhello.py\n__init__.py\n__pycache__\nsetup.py\nsimple_markup.py\nstt\ntest_input.txt\ntest_output.html\ntest.py\nutil.py\n'

没有指定stdout或stderr时,输出到屏幕:

In [6]: ret = subprocess.run(["ls"],shell=True,encoding="utf-8",timeout=1)                                                   
build  dist  Hello.egg-info  __init__.py  setup.py	    stt		    test_output.html  util.py
cs     fifo  hello.py	     __pycache__  simple_markup.py  test_input.txt  test.py
  • cwd: 指定子进程的工作目录,在子进程执行之前会切换过去

  • timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出 TimeoutExpired 异常。

  • check:如果该参数设置为 True,并且进程退出状态码不是 0,则弹 出 CalledProcessError 异常。

  • shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。

如果传递的args为字符串,则shell必须为True;

subprocess.run("ls -l", shell=True).stdout

如果字符串只有要执行的程序名,但无任何参数shell可以为False

subprocess.run("ls", shell=False).stdout

注意:
subprocess.run([“cd”, “…”], shell=False).stdout # pointless code!,因为一个进程无法更改另一个进程的工作目录
切换工作目录的办法:

subprocess.run(["ls"], shell=True, cwd='/').stdout

wd = os.getcwd()
os.chdir("/")
subprocess.run(["ls"])
os.chdir(wd)

CompletedProcess实例

可获取子进程的执行结果:returncode是子进程执行后的状态码,stdout是子进程的标准输出,及stderr错误输出

In [111]: ret = subprocess.run(["ls"],shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8",timeout=1)   

In [112]: ret                                                                                                                
Out[112]: CompletedProcess(args=['ls'], returncode=0, stdout='build\ncs\ndist\nHello.egg-info\nhello.py\n__init__.py\n__pycache__\nsetup.py\nsimple_markup.py\nstt\ntest_input.txt\ntest_output.html\ntest.py\nutil.py\n', stderr='')
  • args
    被用于启动该子进程的字符串或列表

  • returncode
    子进程的退出状态码. 通常来说, 一个为 0 的退出码表示进程运行正常;1则代表执行失败
    一个负值 -N 表示子进程被信号 N 中断 (仅 POSIX).

  • stdout
    从子进程捕获到的标准输出。如果 run() 是设置了 encoding, errors 或者 text=True 来运行的,则会获得字符串;没有任何以上设置则会获得字节串。
    如果没有捕获,则返回None
    如果run()通过 stderr=subprocess.STDOUT 运行, 标准输入和标准错误将被组合在一起, 并且 stderr将为 None.

  • stderr
    捕获到子进程的标准错误。捕获到的形式同stdout

class subprocess.Pope(…)

创建新进程并执行子程序,相比于subprocess.run()函数,Pope可以用来与子进程进行复杂交互式操作。
缺点:相对于subprocess.run()更底层,使用起来较为复杂

subprocess.Popen(args[, bufsize, stdin, stdout, stderr, …]):Popen类的构造函数,返回结果为subprocess.Popen对象
;参数与subprocess.run的类似

Popen对象的方法

poll() :用于检查命令是否已经执行结束,若结束返回returncode;若未结束返回None;
PopenObject.send_signal(signal):发送信号signal给子进程;

In [44]: cc = subprocess.Popen("python", stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)                          

In [45]: cc.poll()                                                                                                           

In [46]: cc.send_signal(3)    # 发送一个3号信号SIGQUIT:终止程序     

In [47]: cc.poll()                                                                                                           
Out[47]: -3

terminate():停止子进程;
kill():杀死子进程;

In [48]: cc = subprocess.Popen("python", stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)                          

In [49]: cc.kill()                                                                                                           

In [50]: cc.poll()                                                                                                           
Out[50]: -9

In [51]: cc = subprocess.Popen("python", stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)                          

In [52]: cc.terminate()                                                                                                      

In [53]: cc.poll()                                                                                                           
Out[53]: -15

wait([timeout, endtime]):阻塞父进程,等待子进程结束,并返回returncode; 若超过timeout(s)进程仍未结束,则抛出异常

In [54]: cc = subprocess.Popen("python", stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)                          

In [55]: cc.wait() 

communicate(input=None, timeout=None):
进程交互:发送数据给stdin,从std何stderr读取数据(直到遇到EOF)
该方法会阻塞父进程,直到子进程返回returncode, Communicate()返回一个元组:(stdoutdata, stderrdata),分别保存输出数据和错误信息
input: 发送给子进程的数据,如果以文本模式打开流,则输入必须为字符串。否则,它必须是字节串。
timeout: 进程timeout后没有终结,会抛出 TimeoutExpired 异常, 但紫禁城不会被kill,因此应kill子进程并完成通信

proc = subprocess.Popen(...)
try:
    outs, errs = proc.communicate(timeout=15)
except TimeoutExpired:
    proc.kill()
    outs, errs = proc.communicate()

使用communicat方法写数据到子进程,并读取输出

In [5]: cc = subprocess.Popen("python", stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)                           

In [6]: out, error = cc.communicate(b"print('hello world')")                                                                 

In [7]: out                                                                                                                  
Out[7]: b'hello world\n'

注意:
1、如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE
2、

Popen对象的属性

stdin:
若PopenObject中stdin为PIPE,则返回一个可写流对象;若encoding或errors参数被指定或universal_newlines参数为True,则此流是一个文件流,否则为字节流。
若PopenObject中stdin不是PIPE,则属性为None。
stdin输入流非None,可执行写操作即PopenObject.stdin.write(s)

stdout:
若PopenObject中stdout为PIPE,则返回一个可读流对象;可执行读操作即PopenObject.stdout.read()或.readlines(), 其它同stdin

stderr:
若PopenObject中stderr为PIPE,则返回一个可读流对象;可执行读操作即PopenObject.stdout.read()或.readlines(), 其它同stdin

使用Popen对象的stdin写数据到子进程,并使用stdout和stderr的读输出

import subprocess

s = subprocess.Popen("python", stdout=subprocess.PIPE, stderr= subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
with s.stdin:
    s.stdin.write(b"import os\n")
    s.stdin.write(b"print(os.environ)")

with s.stdout:
    out = s.stdout.read().decode("GBK")

with s.stderr:
    error = s.stderr.read().decode("GBK")

print(f'out={out}')
print(f'error={error}')

pid: 获取子进程的id
returncode: 获取进程的返回值,如果进程还没有结束,则返回None

其它

Popen的wait方法和communicate方法比较

使用 subprocess 模块的 Popen 调用外部程序,如果 stdout 或 stderr 参数是 PIPE,并且程序输出超过操作系统的 pipe size时,
如果使用 Popen.wait() 方式等待程序结束获取返回值,会导致死锁,程序卡在 wait() 调用上。
官方文档里推荐使用 Popen.communicate()。这个方法会把输出放在内存,而不是管道里,所以这时候上限就和内存大小有关了

ulimit -a 看到的 pipe size 是 4KB,那只是每页的大小,查询得知 Linux 默认的 pipe size 是 64KB

#!/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)
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值