zoukankan      html  css  js  c++  java
  • python 标准类库-并行执行之subprocess-子进程管理

    标准类库-并行执行之subprocess-子进程管理

     

    by:授客QQ1033553122

    1.使用subprocess模块

    以下函数是调用子进程的推荐方法,所有使用场景它们都能处理。也可用Popen以满足更高级的使用场景

    subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

    运行args描述的命令,等待命令完成后返回returncode属性。

     

    timeout参数会传递Popen.wait()。如果超过timeout,子进程将会被kill掉,并再次等待。子进程被终止后会抛出TimeoutExpired异常。

     

    Eg:

    >>>returncode = subprocess.call('exit 1', shell=True)
    print(returncode)# 输出1

    >>> returncode = subprocess.call(
    'exit 0', shell=True)
    print(returncode)# 输出0

     

    注意:针对该函数,不要使用stdout=PIPE stderr=PIPE。因为不是从当前进程中读取管道(pipe),如果子进程没有生成足够的输出来填充OS的管道缓冲区,可能会阻塞子进程。

     

     

    subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

    运行携带参数的命令,等待命令完成。如果返回代码为0,则返回,否则抛出 CalledProcessError。返回代码将被赋值给CalledProcessErrorreturncode属性

     

    timeout参数会传递Popen.wait()。如果超过timeout,子进程将会被kill掉,并再次等待。子进程被终止后会抛出TimeoutExpired异常。

     

    Eg:

    >>> subprocess.check_call(["ls", "-l"]) # run on linux only
    
    0
    
     
    
    >>> subprocess.check_call('exit 0', shell=True)
    
    0
    
     
    
    >>> subprocess.check_call('exit 1', shell=True)
    
    Traceback (most recent call last):
    
    ……
    
    subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
    
     
    

     

    注意:针对该函数,不要使用stdout=PIPE stderr=PIPE。因为不是从当前进程中读取管道(pipe),如果子进程没有生成足够的输出来填充OS的管道缓冲区,可能会阻塞子进程。

     

     

    subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None)

    运行携带参数的命令,并返回输出

    如果返回代码为b不为0,则抛出 CalledProcessError。返回代码将被赋值给CalledProcessErrorreturncode属性,且任意输出将会被存放在output属性。

    timeout参数会传递Popen.wait()。如果超过timeout,子进程将会被kill掉,并再次等待。子进程被终止后会抛出TimeoutExpired异常。

    Eg:

    >>> subprocess.check_output(['echo', 'hello world'], shell=True)
    
    b'"hello world"
    '
    
     
    
    >>> subprocess.check_output(['echo', 'hello world'], universal_newlines=True, shell=True)
    
    '"hello world"
    '
    
     
    
    >>> subprocess.check_output('exit 1', shell=True)
    
    Traceback (most recent call last):
    
    ……
    
    subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
    
    >>> 
    
     
    

     

    默认的,该函数会返回编码的字节。实际输出的编码可能依赖被调用的命令。 所以,对于输出text的解码经常需要在应用层处理。可通过设置universal_newlines True来覆盖编码行为。

     

    也可以通过使用stderr=subprocess.STDOUT在结果中捕获标准错误。

    Eg:

     

    >>> subprocess.check_output(
    
            'dir non_existent_dir | exit 0',
    
            stderr=subprocess.STDOUT,
    
            universal_newlines=True,
    
            shell=True)
    
    '找不到文件
    '
    

     

     

    注意:针对该函数,不要使用stderr=PIPE。因为不是从当前进程中读取管道(pipe),如果子进程没有生成足够的输出来填充OS的管道缓冲区,可能会阻塞子进程。

     

     

    subprocess.DEVNULL

    可用于Popen函数stdinstdout或者stderr参数的特定值,表示使用指定文件os.devnull

     

    subprocess.PIPE

    可用于Popen函数stdinstdout或者stderr参数的指特定值,表示必须打开一个指向标准流的管道。

     

     

    subprocess.STDOUT

    可用于Popen函数stdinstdout或者stderr参数的指特定值,表示标准错误信息必须一起写入同样的句柄,比如标准输出。

     

    exception subprocess.SubprocessError

    来自该模块的所有异常的父类。

     

    exception subprocess.TimeoutExpired

    SubprocessError的子类,当等待子进程timeout超时抛出

     

    cmd

    用于衍生子进程的命令。

     

    timeout

    以秒wield单位的超时时间。

     

    output

    如果异常由check_output抛出,则存放子进程的输出。否则None

     

    exception subprocess.CalledProcessError

    SubprocessError的子类,当check_call() check_output()运行的进程退出时,返回非0值时抛出。

     

    returncode

    子进程的退出状态

     

    cmd

    用于衍生子进程的命令。

     

    output

    如果异常由check_output抛出,则存放子进程的输出。否则None

     

    2.频繁使用的参数

    以下是Popencallcheck_callcheck_output等函数最常使用的参数:

    args 所有调用的必填参数,参数值为字符串、序列。处于方便,通常更偏向于提供序列。如果传递的是单一字符串,shell参数值必须为True,如果不提供其它任何参数,传递单一字符串的情况下,该字符串必须是需要执行的程序名。

     

    stdin, stdoutstderr分别指明了被执行程序的标准输入,标准输出和标准错误处理文件句柄。可选值PIPEDEVNULL,已存在文件描述符(一个正整数),已存在文件对象,NonePIPE表示应该创建通往子进程的管道。DEVNULL表示应该使用指定文件os.devnull。默认参数None则表示无进行重定向,子进程文件句柄从父进程继承。此外,stderr还可以是STDOUT,表明子进程的错误数据应该被放进相同的文件句柄stdout

     

    如果universal_newlinesTrue,文件对象stdinstdoutstderr将按universal newlines(Unix 行结束符:' ',  Windows行结束符:' ')模式,使用locale.getpreferredencoding(false)(函数会根据用户偏好设置,返回使用的文本数据的编码)返回的编码,以文件流的方式打开。

     

    如果shellTrue,指定命令将通过shell执行。出于安全考虑,如果命令字符串参数需要通过外部的输入来构成的时候,强烈建议设置shell=False,不然容易造成shell注入之类的,如下

     

    from subprocess import call

    if __name__ == '__main__':
    dirname =
    input('which dir would you like to cd in? ')
        call(
    'cd ' + dirname, shell=True)
     
    

    运行结果

    3. 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=False, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=())

    在一个新进程中执行子进程。类似在Unix上使用os.execvp()Windows上使用CreateProcess()函数。

     

    args 参数值为字符串、序列。默认的,如果args是个序列,程序会执行args中第一项。如果args是字符串则根据平台而异,如下所述。无特殊需求,建议传递序列。

     

    注意:可用shlex.split()来判断正确的args分割,特别是复杂的情况

    Eg:

    >>> 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'"]
    
    >>> subprocess.Popen(args)
    

     

     

    Windows上,如果args为序列,那么将会按照以下规则进行转换为一个字符串,因为后台函数 CreateProcess() 的操作依赖字符串。

    1. 参数由空白字符(空格或tab)分隔。
    2. 通过双引号标记的字符串被解释为单个参数,不管字符串中是否包含空白字符。
    3. "被当作是字面字符 ",即转义字符
    4. 除非后面跟了双引号,如",否则还是被解释为字面字符
    5. \被解释为\"则被解释为一个 和一个 " 字符

    shell参数用于指明是否使用shell作为执行程序。默认False。如果sellTrue,则推荐传递字符串参数给args

     

    Unix操作系统上,shell=Trueshell默认为/bin/sh。如果args为字符串,则字符串指明了需要通过shell执行的命令。这意味着字符串必须具备准确的格式,正如在shell终端中输入的一样。如果args为序列,则第一项指定命令字符串,其它额外项则被当作额外参数,等同于说

    Popen(['/bin/sh', '-c', args[0], args[1], ...])
    

     

    Windows如果shell=TrueCOMSPCE环境变量指定了默认的shell。仅在command 命令需通过shell执行,比如dir,copy命令时,使用shell=True。不必要通过设置shell=True,来运行一批处理或者基于控制的可执行程序。

     

    bufsize 当创建stdin/stdout/stderr管道文件对象时,bufsize将作为io.open()函数的对应的参数: 0 - 意味着未缓冲 means unbuffered (read and write are one system call and can return short)

    1 - 意味着行缓冲(means line buffered

    任意正数 - 使用缓冲,缓冲大小和给定正数大致相等。

    任意负数 - 使用缓冲,缓冲大小等于系统自带的o.DEFAULT_BUFFER_SIZE

     

    Executable

     

    executable参数指定了用于执行的替代程序。很少用到。

     

    stdin, stdout stderr

    分别指定被执行程序的标准输入,标准输出,标准错误文件句柄。合法值为PIPE,DEVNULL,已存在文件描述符(一个正整数),已存在文件对象和None PIPE表示应该创建通往子进程的管道。DEVNULL表示应该使用指定文件os.devnull。默认参数None则表示无进行重定向,子进程文件句柄从父进程继承。此外,stderr还可以是STDOUT,表明子进程的错误数据应该被放进相同的文件句柄stdout

     

    preexec_fn

    如果preexec_fn 被设置为可调用对象,该对象将在子进程执行之前被执行(仅限Unix)。

     

    close_fds

    如果close_fdsTrue, 所有文件描述符,012除外都在子进程执行前被关闭(仅限Unix)。 (Unix only). 默认值根据平台而异。Unix平台总是TrueWindows平台,当stdin/stdout/stderrNone时,为True,否则为FalseWindows平台,如果close_fdsTrue,那么子进程不会继承任何句柄。

     

    universal_newlines

    如果universal_newlinesTrue,文件对象stdinstdoutstderr将按universal newlines(Unix 行结束符:' ',  Windows行结束符:' ')模式,使用locale.getpreferredencoding(false)(函数会根据用户偏好设置,返回使用的文本数据的编码)返回的编码,以文件流的方式打开。

     

    ……

     

    可配合with使用,退出时,先关闭标准文件描述符,如下

    import subprocess
    
     
    
    if __name__ == '__main__':
    with subprocess.Popen(['dir'], stdout=subprocess.PIPE, shell=True, universal_newlines = True) as proc:
    print(proc.stdout.read())

     

    输出

    python <wbr>标准类库-并行执行之subprocess-子进程管理

    更多参考官方文档

     

    4.Popen对象

    Popen类实例有以下几个方法

     

    Popen.poll()

    检测子进程是否中断,设置并返回returncode

     

    Popen.wait(timeout=None)

    等待子进程终止,设置并返回returncode。如果进程在timeout(单位 秒)之后依然没终止,则抛出TimeoutExpired 异常,可以捕获该异常并再次尝试等待。

     

    警告

    当使用stdout=PIPE and/or stderr=PIPE时,如果子进程生成足够的输出到管道,这会阻止操作系统管道缓冲区接收更多数据,进而造成死锁。为了避免该事件,使用communicate()

     

    Popen.communicate(input=None, timeout=None)

    process交互:发送数据到stdin,从stdout,stderr读取数据,直到文件结束符。等待子进程终止。

     

    input:可选参数,参数值为发送给子进程的数据,如果不需要发送数据,则为None。如果universal_newlinesFalse,则input数据类型必须为字节,否则可为字符串。

     

    函数返回一个元组(stdoutdata, stderrdata)

     

    注意,如果想发送数据到进程管道,必须在创建Popen对象时使用stdin=PIPE,类似的如果想从结果元组中获取非None值数据,创建Popen对象时需要提供stdout=PIPE and/or stderr=PIPE参数。

     

    如果进程在timeout(单位 秒)之后依然没终止,则抛出TimeoutExpired 异常,(Python3.3.2中发丝。捕获该异常并重试comunicate,不会丢失任何输出。

     

    如果超过timeout,子进程不会被kill掉,所以为了完成交互,恰当的清理友好执行的程序,应该kill子进程。


    import subprocess
     
    
    if __name__ == '__main__':
    with subprocess.Popen(['dir'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines = True) as proc:
    try:
                outs, errs = proc.communicate(
    timeout=15) #超时时间为15
    print(outs, errs)
    except subprocess.TimeoutExpired:
                proc.kill()
                outs, errs = proc.communicate()
    print(outs, errs)

     

     

     

    注意:读取的数据缓存在内存,所以如果数据太大或者无限制,不要使用该函数。

     

    Popen.send_signal(signal)

    发送signal给子进程

     

    Popen.terminate()

    停止子进程。

     

    Popen.kill()

    Kill子进程。 Posix操作系统:函数会发送SIGKILL给子进程。Windowskill()terminate()别名。

     

    以下为属性:

    注意:使用communicate() 而非.stdin.write, .stdout.read 或者.stderr.read以避免死锁。

     

    Popen.stdin

    如果stdin参数为PIPE,该属性为给子进程提供输入的文件对象, 否则为None.

     

    Popen.stdout

    如果stdin参数为PIPE,该属性为给子进程提供正确输出的文件对象,否则为None.

     

    Popen.stderr

    如果stdin参数为PIPE,该属性为给子进程提供错误输出的文件对象,否则为None.

     

    Popen.pid

    子进程的ID

     

    注意:如果设置了shell=True,则该属性值为衍生的shell进程的id

     

    Popen.returncode

    子进程返回代码,如果值为None表明进程还没终止。负值-N表示子进程通过signal N终止的(仅限Unix)

     

    Eg:

    import sys
    import  subprocess

    def run_command():
        cmd = [sys.executable,
    'py1.py']
        proc = subprocess.Popen(cmd,
    stdout=subprocess.PIPE, universal_newlines = True, stderr=subprocess.STDOUT)
        outs, errs = proc.communicate()
    # print(proc.communicate()) #报错,因为文件已经关闭
    print(outs)

    if __name__ == '__main__':
        run_command()

     

    运行结果:

    python <wbr>标准类库-并行执行之subprocess-子进程管理

     

    注:py1.pystudy.py在同一个目录下,内容如下

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-

    __author__ = 'laiyu'

    print('output from py1.py')

     

    5.替换老函数

    可用callPopen替换一些老函数如os.system()os.spawn家族系列,shell管道等,具体参考官方文档

     

  • 相关阅读:
    做汉堡
    第三次作业:五章感想与问题
    第二次作业:结对练习
    自己要的东西
    存在不知道是什么问题的程序
    第一个Sprint冲刺第二天
    第一个Sprint冲刺第一天
    第三个Sprint完结工作 用场景来规划测试工作.
    beta 阶段的 postmortem 报告
    重新评估团队贡献分
  • 原文地址:https://www.cnblogs.com/shouke/p/10157615.html
Copyright © 2011-2022 走看看