• python的subprocess的简单使用和注意事项


    subprocess是python在2.4引入的模块, 主要用来替代下面几个模块和方法:

    os.system
    os.spawn*
    os.popen*
    popen2.*
    commands.*

    可以参考PEP324: http://legacy.python.org/dev/peps/pep-0324/

    这是一个用来调用外部命令的模块, 替代了一些旧的模块, 提供了更加友好统一的接口.

    三个封装方法

    使用下面三个方法的时候, 注意两个问题: 1. shell=True或False, 两种解析方式是不同的 2. 注意PIPE的使用, 可能导致卡死

    subprocess.call 运行命令, 等待完成, 并返回returncode

    subprocess.check_call 运行命令, 等待完成, 如果返回值为0, 则返回returncode, 否则抛出带有returncode的CalledPorcessError异常.

    subprocess.check_output 和check_call类似, 会检查返回值是否为0, 返回stdout.

    卡死常见的原因

    这个模块在使用的时候, 可能会出现子进程卡死或hang住的情况. 一般出现这种情况的是这样的用法.

    import subprocess
    import shlex
    
    proc = subprocess.Popen(shlex.split(cmd), stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                            shell=False, universal_newlines=True)
    print proc.stdout.read()
    

    这里的直接读取了Popen对象的stdout, 使用了subprocess.PIPE. 这种情况导致卡死的原因是PIPE管道的缓存被充满了, 无法继续写入, 也没有将缓存中的东西读出来.

    官方文档的提示(Popen.wait()方法的Warning)

    This will deadlock when using stdout=PIPE and/or stderr=PIPE and the child process generates enough output to a pipe such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that.

    为了解决这个问题, Popen有一个communicate方法, 这个方法会将stdout和stderr数据读入到内存, 并且会一直独到两个流的结尾(EOF). communicate可以传入一个input参数, 用来表示向stdin中写入数据, 可以做到进程间的数据通信.

    注意: 官方文档中提示, 读取的数据是缓存在内存中的, 所以当数据量非常大或者是无限制的时候, 不要使用communicate, 应该会导致OOM.

    一般情况下, stdout和stderr数据量不是很大的时候, 使用communicate不会导致问题, 量特别大的时候可以考虑使用文件来替代PIPE, 例如stdout=open("stdout", "w")[参考1].

    参考2中给出了另一种解决的思路, 使用select来读取Popen的stdout和stderr中的数据, select的这种用法windows下是不支持的, 不过可以做到比较实时的读取数据.

    Popen中的shell参数含义

    官方文档中推荐shell=False, 这种方式更加安全, 我们来看一下官方给出的例子. 

    >>> from subprocess import call
    >>> filename = input("What file would you like to display?
    ")
    What file would you like to display?
    non_existent; rm -rf / #
    >>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...
    

    上面这种命令拼写的方式会导致一个安全问题, 就是用户可以进行类似sql注入的shell注入, 删除整个系统的文件, 这个是极其危险的.

    shell=False会屏蔽shell中的很多特性, 所以可以避免上述这种安全问题, 当需要暴露给用户去使用的时候, 尤其要注意上述的安全问题.

    shell=True的时候就是按照shell的方式解析和运行的.

    Popen的一些简单调优思路

    有个bufsize的参数, 这个默认是0, 就是不缓存, 1表示行缓存, 其他正数表示缓存使用的大小, 负数-1表示是使用系统默认的缓存大小.

    在运行性能遇到问题时, 可能是缓存区未开启或者太小, 导致了子进程被卡住, 效率低下, 可以考虑配置为-1或4096.

    需要实时读取stdout和stderr的, 可以查阅[参考2], 使用select来读取stdout和stderr流.

    参考: 

    1. 小心subprocess的PIPE卡住你的python程序: http://noops.me/?p=92
    2. pipe large amount of data to stdin while using subprocess.Popen: http://stackoverflow.com/questions/5911362/pipe-large-amount-of-data-to-stdin-while-using-subprocess-popen
    3. python docs: http://docs.python.org/2/library/subprocess.html
  • 相关阅读:
    Building Apache Thrift on CentOS 6.5
    ToStringBuilder 学习
    对List中对象的去重
    MyEclipse启动Tomcat服务器时老是跳到Debug调试上
    JS 实现点击展开菜单
    详解公钥、私钥、数字证书的概念 转载
    eclipse svn 忽略 target目录 等等... 我用的后边的方法 (转载)
    Log4j XML 配置
    JS完成改变新闻字体大中小的显示
    Javascript 简单学习
  • 原文地址:https://www.cnblogs.com/icejoywoo/p/3627397.html
Copyright © 2020-2023  润新知