Python Module_subprocess_子进程(程序调用)

简介: 目录目录前言软件环境认识subprocessPopen Constructor构造函数Class Popen的参数args调用程序调用Shell指令stdinstdoutstderr实时获取子程序输出一次获取子程序的全部输出将标准错...

目录

前言

subpocess用于在父进程中创建子进程,如果你希望在Python程序中调用外部程序,如:Powershell、shell、cmd、bat。subprocess将会是一个非常好的选择。

软件环境

  • 系统
    • Win 10
  • 软件
    • Python 3.4.4
    • IPython 4.0.0

认识subprocess

还是那句话,最高效的方法不过看官方文档,传送门:这里
subprocess:The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several older modules and functions:

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

subprocess的诞生是为了替代、整合以前几种旧的创建子进程的方法,能够实现以管道的形式连接子进程的stdinstdoutstderr,并且子进程会返回一个returncode给父进程。与C语言中的fock实现类似的功能。
Execute a child program in a new process. On POSIX, the class uses os.execvp() like behavior to execute the child program. On Windows, the class uses the Windows CreateProcess() function
在POSIX类型系统中会使用os.execvp()来执行子程序,而在windows环境中会使用CreateProcess()函数来执行,这篇博文主要记录在windows环境下的使用。

Popen Constructor(构造函数)

subprocess拥有数个通过不同的方式来创建子进程的函数,但是subprocess只有一个Popen类,同样是用于创建子进程。使用Class Popen创建的对象拥有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=())

在Python 3.2之后的版本Popen对象添加了下面这种写法:
on exit, standard file descriptors are closed, and the process is waited for.

with Popen(["ifconfig"], stdout=PIPE) as proc:
    log.write(proc.stdout.read())

下面介绍Popen类的参数含义。

Class Popen的参数

args

args :should be a sequence of program arguments or else a single string.
args参数可以是String类型或者sequence类型,作为子程序的声明。在Windows中调用的子进程API是CreateProcess([String]),所以可以接受诸如notepad.exe test.txt这样的字符串来执行。但是在Linux的环境下需要接受List类型对象来分隔程序名和参数。如果是序列类型,序列的第一个参数一般也作为子程序的路径,之后的元素作为传入子程序的参数。官方建议args参数使用List类型对象

调用程序

调用一个Powershell脚本程序:

args = [r"C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe","-ExecutionPolicy","Unrestricted",r"E:\Users\oe-fanguiju\Desktop\SendMail.ps1",str(bodyStr)]
ps = subprocess.Popen(args,stdout=subprocess.PIPE)

注意:在String前加入r是为了避免出现SyntaxError: (unicode error)

调用Shell指令

序列化传入参数 shlex.split()
我们还可以使用shlex.split()函数来将我们所需要执行的指令序列化后再赋值给args参数。

>>> 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!

stdin\stdout\stderr

stdin\stdout\stderr指定了子程序的标准输入、输出、错误的文件句柄(file handles)。他们可以是PIPE管道、DEVNULL(DEVNULL indicates that the special file os.devnull will be used.)、文件描述符(existing file descriptor 一个正整数)或已存在的文件对象(file object)。当他的值是None时,不会发生重定向,子进程的文件句柄将会继承父进程。而且stderr=subprocess.STDOUT能够将标准错误的文件句柄设成标准输出(子程序的标准错误汇合到标准输出)。将stdout/stderr指定为subprocess.PIPE,这样在Popen被调用的时候会在父进程和子进程之间建立管道,子进程的标准输出和错误输出都重定向到管道,可以被父进程获取。

实时获取子程序输出

p = subprocess.Popen("/etc/service/tops-cmos/module/hadoop/test.sh", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
returncode = p.poll()    
while returncode is None:   #检查子程序是否结束
        line = p.stdout.readline()    #若没有,则获取子程序的输出
        returncode = p.poll()
        line = line.strip()
        print line
print returncode

这样就可以实时的获取子进程的输出。

一次获取子程序的全部输出

当你希望在子程序执行完后一次性获取所有子进程输出时,子进程对象可以调用communicate(),他会一直阻塞,等待子进程结束后获取子进程返回的输出。

ps = subprocess.Popen(args,stdout=subprocess.PIPE)
psAllReturn = ps.communicate()
#Or: psReturn = ps.stdout.read()
return psReturn

只有当子程序执行结束的后才会返回执行结果。
注意:communicate()会在通信一次之后即关闭了管道。如果希望进程之间频繁的通信,并不建议这种方法。
可以尝试下面的方法:

p= subprocess.Popen(["wc"], stdin=subprocess.PIPE,stdout=subprocess.PIPE,shell=True)  
p.stdin.write('your command')   #传递数据给子进程
p.stdin.flush()                 #清空子进程管道缓存
#......do something   
try:  
    #......do something  
    p.stdout.readline()         #获取子进程输出
    #......do something  
except:  
    print('IOError')  


#......do something more  

p.stdin.write('your other command')  
p.stdin.flush()  
#......do something more 

将标准错误和标准输出一起输出

ps = subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)

这样无论是标准输出还是标准错误输出都会经过stdout管道返回给父进程。

shell

The shell argument (which defaults to False) specifies whether to use the shell as the program to execute. If shell is True, it is recommended to pass args as a string rather than as a sequence.
shell指定是否使用shell程序来执行子进程,当shell = True时,子进程将会由shell来执行,并且建议args参数使用String对象。
If args is a string, the string specifies the command to execute through the shell.

In [19]: p = subprocess.Popen("ipconfig /all",stdout = subprocess.PIPE,shell=True)  #args = "ipconfig /all"

In [20]: pRet = p.stdout.read()  #父进程获得执行结果

#On Windows with shell=True, the COMSPEC environment variable specifies the default shell.
#Win下相当于```args = ["cmd.exe","ipconfig","/all"]

On POSIX with shell=True, the shell defaults to /bin/sh.
Linux下相当于args = ["/bin/sh"," -c",args[0], args[1], ...]

bufsize

bufsize will be supplied as the corresponding argument to the open() function when creating the stdin/stdout/stderr pipe file objects.
0 means unbuffered (read and write are one system call and can return short)
1 means line buffered (only usable if universal_newlines=True i.e., in a text mode)
any other positive value means use a buffer of approximately that size
negative bufsize (the default) means the system default of io.DEFAULT_BUFFER_SIZE will be used.
当你将stdin/stdout/stderr重定向到PIPE或文件对象时,bufsize能够传递指定缓冲的方式给open()函数,并以此来创建文件对象:
0 表示无缓冲;
1 表示行缓冲;
otherNumber 表示缓冲区大小,
-1 是bufsize参数的缺省值表示使用系统缓冲(全缓冲)
注意:当子进程返回的数据达到缓存的Size时,子程序会等待付进程读取缓存数据。

close_fds

If close_fds is true, all file descriptors except 0, 1 and 2 will be closed before the child process is executed. (POSIX only). The default varies by platform: Always true on POSIX. On Windows it is true when stdin/stdout/stderr are None, false otherwise. On Windows, if close_fds is true then no handles will be inherited by the child process. Note that on Windows, you cannot set close_fds to true and also redirect the standard handles by setting stdin, stdout or stderr.
在Unix中,如果close_fds = True,除了0、1、2之外的文件描述符都会被关闭。如果在Windows中stdin/stdout/stderr = None,即继承父进程时,close_fds = True ,相反为False。在Windows下也不会继承其他文件描述符。
注意:在Windows中不能够在close_fds = True的前提下对stdin, stdout or stderr做重定向(redirect ),锁定子进程的stdin/stdout/stderr。

In [24]: p = subprocess.Popen("ipconfig",shell=True,close_fds=True,stdout=subprocess.PIPE)

ValueError: close_fds is not supported on Windows platforms if you redirect stdin/stdout/stderr

其他参数含义

given, startupinfo:If given, startupinfo will be a STARTUPINFO object, which is passed to the underlying CreateProcess function. creationflags, if given, can be CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP. (Windows only)
在Windows中,调用CreateProcess()函数创建子进程时,startupinfo参数会传递STARTUPINFO对象,来设置子进程的外观等等属性。

executable:指定要执行的程序名,很少使用,一般用args参数来指定需要执行的子程序。可以指定执行子进程的shell(e.g. bash、csh、zsh)。Unix下,默认是/bin/sh。Windows下,就是环境变量%COMSPEC%的值。windows下,只有当你要执行的命令确实是shell内建命令(比如dir ,copy)时,你才需要指定shell=True
,而当你要执行一个基于命令行的批处理脚本的时候,不需要指定此项。

C:\Users\Username>echo %COMSPEC%
C:\WINDOWS\system32\cmd.exe

preexec_fn:If preexec_fn is set to a callable object, this object will be called in the child process just before the child is executed. (POSIX only)
钩子函数,只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用。容易造成死锁,慎用。

cmd:指定了子进程的工作目录
注意:并不会把该目录做为可执行文件的搜索目录,所以不要把子程序文件所在目录设置为cwd 。

env:是一个字典类型,用于执行子进程的执行环节变量,而不使用默认继承父进程的环境变量

universal_newlines:为True时,子进程的stdout和stderr被视为文本对象,不管是Unix的行结束符’/n’,还是Mac格式的行结束符’/r’,还是Windows格式的行结束符’/r/n’都将被视为 ‘/n’处理 。

pass_fds:is an optional sequence of file descriptors to keep open between the parent and child. Providing any pass_fds forces close_fds to be True. (POSIX only)

restore_signals:If restore_signals is true (the default) all signals that Python has set to SIG_IGN are restored to SIG_DFL in the child process before the exec. Currently this includes the SIGPIPE, SIGXFZ and SIGXFSZ signals. (POSIX only)

start_new_session:If start_new_session is true the setsid() system call will be made in the child process prior to the execution of the subprocess. (POSIX only)

Popen成员函数

Popen.poll()

Check if child process has terminated. Set and return returncode attribute.
用于检查子进程是否已经结束。设置并返回returncode属性

Popen.wait(timeout=None)

Wait for child process to terminate. Set and return returncode attribute.
等待子进程结束。设置并返回returncode属性。使用Popen类创建的子进程时,父进程默认不会等待子进程结束,需要Call wait()函数来实现等待子进程结束后父进程继续执行。
timeout:If the process does not terminate after timeout seconds, raise a TimeoutExpired exception. It is safe to catch this exception and retry the wait.
注意: 如果子进程输出了大量数据到stdout或者stderr的管道,并达到了系统PIPE的缓存大小时,子进程会等待父进程读取管道内的数据,若此时父进程正在wait()的话,将不会读取管道内的数据,从而造成死锁,建议使用Pepon.communicate()来避免这种情况。

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

与子进程进行交互。向stdin发送数据,可选参数input指定发送到子进程的数据。
Communicate()时从stdout和stderr中读取数据,直到文本末尾的EOF(进程之间采用文本通信),等待子进程结束。返回一个元组:(stdout_data, stderr_data),The data will be bytes or, if universal_newlines was True, strings.如果universal_newlines = True则返回一个String。
注意:如果希望通过stdin向子进程发送数据,需要通过stdin=PIPE创建管道对象。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。

p=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
(stdout_data, stderr_data) = p.communicate()

Note:If the process does not terminate after timeout seconds, a TimeoutExpired exception will be raised. Catching this exception and retrying communication will not lose any output.
如果timeout还没有结束进程的话,需要捕捉触发的TimeoutExpired异常,并且使用Popen.communicate()来保留子程序的输出。

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

Popen.send_signal(signal)

向子进程发送信号。

Popen.terminate()

停止子进程。在windows平台下,该方法将调用Windows API TerminateProcess()来结束子进程。

Popen.kill()

杀死子进程,在Windows上调用了TerminateProcess() API。在Unix上相当于发送了信号SIGTERM和SIGKILL。

Popen成员属性

Popen.pid

获取子进程的进程ID。

Popen.returncode

获取进程的返回值。如果进程还没有结束,返回None。

Popen.stdin

If the stdin argument was PIPE, this attribute is a writeable stream object as returned by open()

输入交互

#test.py
import sys  
line = sys.stdin.readline()  
print 'test',line  

#run.py  
from subprocess import *  
p = Popen('./test.py',stdin=PIPE,stdout=PIPE)  
p.stdin.write('say hi/n')  
print p.stdout.readline()  

Popen.stdout

If the stdout argument was PIPE, this attribute is a readable stream object as returned by open().

连续的输入输出交互

# test.py
import sys  
while True:  
    line = sys.stdin.readline()  
    if not line:break  
    sys.stdout.write(line)  
    sys.stdout.flush()  

# run.py
import sys  
from subprocess import *  
proc = Popen('./test.py',stdin=PIPE,stdout=PIPE,shell=True)  
for line in sys.stdin:  
    proc.stdin.write(line)  #子进程的输入
    proc.stdin.flush()      
    output = proc.stdout.readline()    #子进程的输出  
    sys.stdout.write(output)           #父进程的打印

注意: run.py的flush和test.py中的flush,要记得清空缓冲区,否则程序得不到正确的输入和输出

Popen.stderr

If the stderr argument was PIPE, this attribute is a readable stream object as returned by open().

subprocess函数

subprocess函数本质上是对subprocess.Popen的封装,能够简便的创建子进程。当需要创建更加复杂的子进程时,建议使用Popen类,该类会生成子进程对象,且拥有多样化的成员方法和属性。

subprocess.call()

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

父进程等待子进程完成
返回退出信息(returncode,相当于Linux exit code)

In [20]: subprocess.call("ipconfig")
.
.
Out[20]: 0

subprocess.check_call()

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

父进程等待子进程完成
返回0
检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含有returncode属性,可用try…except…来检查

In [25]: subprocess.check_call("config",shell=True)

CalledProcessError: Command 'config' returned non-zero exit status 1

call()和check_call()的区别:两者的区别在于遇到错误的时候处理不一样,call()返回returncode ,check_call()再返回returncode后还会抛出异常。check_call实际上会调用call函数,然后加入了异常处理的情况。两者在本质上都会调用Popen().wait()来等待进程结束并返回returncode

subprocess.check_output()

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

父进程等待子进程完成
返回子进程向标准输出的输出结果
检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含有returncode属性和output属性,output属性为标准输出的输出结果,可用try…except…来检查。

最后

在使用subprocess模块的时候很容易卡死的情况出现,一些避免卡死的思路,我们之后再聊。 : -)

相关文章
|
15天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
2天前
|
存储 索引 Python
Python从入门到精通——1.3.1练习编写简单程序
Python从入门到精通——1.3.1练习编写简单程序
|
2天前
|
Java 数据库连接 数据处理
Python从入门到精通:3.1.2多线程与多进程编程
Python从入门到精通:3.1.2多线程与多进程编程
|
3天前
|
Java Shell Linux
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
|
7天前
|
数据采集 JavaScript 前端开发
使用Python打造爬虫程序之破茧而出:Python爬虫遭遇反爬虫机制及应对策略
【4月更文挑战第19天】本文探讨了Python爬虫应对反爬虫机制的策略。常见的反爬虫机制包括User-Agent检测、IP限制、动态加载内容、验证码验证和Cookie跟踪。应对策略包括设置合理User-Agent、使用代理IP、处理动态加载内容、验证码识别及维护Cookie。此外,还提到高级策略如降低请求频率、模拟人类行为、分布式爬虫和学习网站规则。开发者需不断学习新策略,同时遵守规则和法律法规,确保爬虫的稳定性和合法性。
|
8天前
|
SQL 安全 Go
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
在Python Web开发中,确保应用安全至关重要,主要防范SQL注入、XSS和CSRF攻击。措施包括:使用参数化查询或ORM防止SQL注入;过滤与转义用户输入抵御XSS;添加CSRF令牌抵挡CSRF;启用HTTPS保障数据传输安全;实现强身份验证和授权系统;智能处理错误信息;定期更新及审计以修复漏洞;严格输入验证;并培训开发者提升安全意识。持续关注和改进是保证安全的关键。
17 0
|
9天前
|
调度 Python
Python多线程、多进程与协程面试题解析
【4月更文挑战第14天】Python并发编程涉及多线程、多进程和协程。面试中,对这些概念的理解和应用是评估候选人的重要标准。本文介绍了它们的基础知识、常见问题和应对策略。多线程在同一进程中并发执行,多进程通过进程间通信实现并发,协程则使用`asyncio`进行轻量级线程控制。面试常遇到的问题包括并发并行混淆、GIL影响多线程性能、进程间通信不当和协程异步IO理解不清。要掌握并发模型,需明确其适用场景,理解GIL、进程间通信和协程调度机制。
28 0
|
24天前
|
分布式计算 算法 搜索推荐
优化 Python 程序的五大技巧
本文介绍了优化 Python 程序的五大技巧,涵盖了代码结构优化、算法选择、内置函数利用、库的使用以及并行处理等方面。通过对这些技巧的实践,可以提升 Python 程序的性能和效率,从而更好地满足各类应用的需求。
|
25天前
|
Python
python使用tkinter库,封装操作excel为GUI程序
python使用tkinter库,封装操作excel为GUI程序
|
27天前
|
前端开发 JavaScript 数据管理
描述一个使用Python开发Web应用程序的实际项目经验,包括所使用的框架和技术栈。
使用Flask开发Web应用,结合SQLite、Flask-SQLAlchemy进行数据管理,HTML/CSS/JS(Bootstrap和jQuery)构建前端。通过Flask路由处理用户请求,模块化代码提高可维护性。unittest进行测试,开发阶段用内置服务器,生产环境可选WSGI服务器或容器化部署。实现了用户注册登录和数据管理功能,展示Python Web开发的灵活性和效率。
15 4