8. python subprocess模块

 


8. subprocess

subprocess 是 Python 的一个标准库模块,用于生成新的进程、连接到它们的输入/输出/错误管道,并获取它们的返回码。它是 Python 中用于执行外部命令和与子进程交互的高级接口,功能强大且灵活。

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, 
    preexec_fn=None, 
    executable=None
)
  • 参数解释:
参数名 类型 描述 默认值
args list 或 str 命令及其参数。如果是列表,则直接传递给程序;如果是字符串,则使用 shell 解释器。 必需
shell bool 是否在 shell 环境中执行命令。 False
stdout file descriptor, subprocess.PIPE, int, None 标准输出的处理方式。 None(输出到控制台)
stderr file descriptor, subprocess.PIPE, int, None 标准错误输出的处理方式。 None(输出到控制台)
stdin file descriptor, subprocess.PIPE, int, None 标准输入的处理方式。 None(不提供输入)
universal_newlines bool 如果为 True,则输入和输出被处理为字符串;如果为 False,则被处理为字节。 False
capture_output bool 如果为 True,则捕获标准输出和标准错误输出。 False
text bool 别名 universal_newlines,在 Python 3.7 中引入,用于简化文本模式的使用。 False
cwd str, None 指定工作目录。 当前工作目录
env dict, None 指定环境变量。 当前环境变量
preexec_fn callable, None 在子进程执行前调用的函数。 None
close_fds bool 是否关闭子进程的文件描述符。 True
startupinfo subprocess.STARTUPINFO, None Windows 平台上的启动信息。 None
creationflags int, None Windows 平台上的创建标志。 0
check bool 如果为 True,则命令执行失败时抛出 subprocess.CalledProcessError 异常。 False
encoding str, None 指定输入和输出的编码。 None(使用系统默认编码)
errors str 指定编码错误的处理方式(strict, ignore, replace)。 未指定

以下是一些使用 subprocess.run() 的示例:

示例描述 代码示例
执行命令并捕获输出 subprocess.run(['ls', '-l'], capture_output=True, text=True)
执行命令并检查结果 subprocess.run(['ls', '/non_existent_directory'], capture_output=True, text=True, check=True)
执行命令并指定工作目录 subprocess.run(['ls', '-l'], cwd='/home/user', text=True)
执行命令并指定环境变量 subprocess.run(['python', 'script.py'], env={'PATH': '/usr/bin'})
执行命令并处理编码 subprocess.run(['command'], encoding='utf-8')
  • Popen.wait()和Popen.communicate()方法
    • Popen对象创建后主程序并不会自动等待子进程完成。所以我们必须执行wait或communicate方法,父进程才会等待
    • wait和communicate方法都是用来等待外部程序执行结束并获取返回值,这也意味着调用它们就开始执行外部命令了。
    • 如果stdout或stderr参数都是PIPE,并且程序输出超过操作系统管道长度时,使用wait会导致死锁,因此官方建议使用communicate

subprocess以及常用的封装函数

运行python的时候,我们都是在创建并运行一个进程。像Linux进程那样,一个进程可以fork一个子进程,并让这个子进程exec另外一个程序。在Python中,我们通过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序。
subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

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

**subprocess.check_call()

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

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

这三个函数的使用方法相类似,下面来以subprocess.call()举例说明:

代码如下:

>>> import subprocess
>>> retcode = subprocess.call(["ls", "-l"])
#和shell中命令ls -a显示结果一样
>>> print retcode
0

将程序名(ls)和所带的参数(-l)一起放在一个表中传递给subprocess.call()

shell默认为False,在Linux下,shell=False时, Popen调用os.execvp()执行args指定的程序;shell=True时,如果args是字符串,Popen直接调用系统的Shell来执行args指定的程序,如果args是一个序列,则args的第一项是定义程序命令字符串,其它项是调用系统Shell时的附加参数。

上面例子也可以写成如下:

代码如下:

>>> retcode = subprocess.call("ls -l",shell=True)

在Windows下,不论shell的值如何,Popen调用CreateProcess()执行args指定的外部程序。如果args是一个序列,则先用list2cmdline()转化为字符串,但需要注意的是,并不是MS Windows下所有的程序都可以用list2cmdline来转化为命令行字符串。

**subprocess.Popen()

**

代码如下:

class Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)

实际上,上面的几个函数都是基于Popen()的封装(wrapper)。这些封装的目的在于让我们容易使用子进程。当我们想要更个性化我们的需求的时候,就要转向Popen类,该类生成的对象用来代表子进程。

与上面的封装不同,Popen对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block),举例:

代码如下:

>>> import subprocess
>>> child = subprocess.Popen(['ping','-c','4','blog.linuxeye.com'])
>>> print 'parent process'

从运行结果中看到,父进程在开启子进程之后并没有等待child的完成,而是直接运行print。

对比等待的情况:

代码如下:

>>> import subprocess
>>> child = subprocess.Popen('ping -c4 blog.linuxeye.com',shell=True)
>>> child.wait()
>>> print 'parent process'

从运行结果中看到,父进程在开启子进程之后并等待child的完成后,再运行print。
此外,你还可以在父进程中对子进程进行其它操作,比如我们上面例子中的child对象:代码如下:

child.poll() # 检查子进程状态
child.kill() # 终止子进程
child.send_signal() # 向子进程发送信号
child.terminate() # 终止子进程

子进程的PID存储在child.pid
二、子进程的文本流控制
子进程的标准输入、标准输出和标准错误如下属性分别表示:

代码如下:

child.stdin
child.stdout
child.stderr

可以在Popen()建立子进程的时候改变标准输入、标准输出和标准错误,并可以利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe),如下2个例子:

代码如下:

>>> import subprocess
>>> child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
>>> print child1.stdout.read(),
#或者child1.communicate()
>>> import subprocess
>>> child1 = subprocess.Popen(["cat","/etc/passwd"], stdout=subprocess.PIPE)
>>> child2 = subprocess.Popen(["grep","0:0"],stdin=child1.stdout, stdout=subprocess.PIPE)
>>> out = child2.communicate()

subprocess.PIPE实际上为文本流提供一个缓存区。child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走。child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。
注意:communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成

subprocess 模块首先推荐使用的是它的 run 方法,更高级的用法可以直接使用 Popen 接口。

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:表示要执行的命令。必须是一个字符串,字符串参数列表。
  • stdin、stdout 和 stderr:子进程的标准输入、输出和错误。其值可以是 subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。subprocess.PIPE 表示为子进程创建新的管道。subprocess.DEVNULL 表示使用 os.devnull。默认使用的是 None,表示什么都不做。另外,stderr 可以合并到 stdout 里一起输出。
  • timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出 TimeoutExpired 异常。
  • check:如果该参数设置为 True,并且进程退出状态码不是 0,则弹 出 CalledProcessError 异常。
  • encoding: 如果指定了该参数,则 stdin、stdout 和 stderr 可以接收字符串数据,并以该编码方式编码。否则只接收 bytes 类型的数据。
  • shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。

run 方法调用方式返回 CompletedProcess 实例,和直接 Popen 差不多,实现是一样的,实际也是调用 Popen,与 Popen 构造函数大致相同,例如:

实例

#执行ls -l /dev/null 命令
>>> subprocess.run(["ls", "-l", "/dev/null"])
crw-rw-rw- 1 root wheel 3, 2 5 4 13:34 /dev/null
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0)

returncode: 执行完子进程状态,通常返回状态为0则表明它已经运行完毕,若值为负值 "-N",表明子进程被终。

简单实例:

实例

import subprocess
def runcmd(command):
ret = subprocess.run(command,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8",timeout=1)
if ret.returncode == 0:
print("success:",ret)
else:
print("error:",ret)

runcmd(["dir","/b"])#序列参数
runcmd("exit 1")#字符串参数

输出结果如下:

success: CompletedProcess(args=['dir', '/b'], returncode=0, stdout='test.py\n', stderr='')
error: CompletedProcess(args='exit 1', returncode=1, stdout='', stderr='')

Popen() 方法

Popen 是 subprocess的核心,子进程的创建和管理都靠它处理。

构造函数:

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=(),
*, encoding=None, errors=None)

常用参数:

  • args:shell命令,可以是字符串或者序列类型(如:list,元组)
  • bufsize:缓冲区大小。当创建标准流的管道对象时使用,默认-1。
    0:不使用缓冲区
    1:表示行缓冲,仅当universal_newlines=True时可用,也就是文本模式
    正数:表示缓冲区大小
    负数:表示使用系统默认的缓冲区大小。
  • stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
  • preexec_fn:只在 Unix 平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
  • shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
  • cwd:用于设置子进程的当前目录。
  • env:用于指定子进程的环境变量。如果 env = None,子进程的环境变量将从父进程中继承。

创建一个子进程,然后执行一个简单的命令:

实例

\>>> import subprocess
\>>> p = subprocess.Popen('ls -l', shell=True)
\>>> total 164
-rw-r--r--  1 root root  133 Jul  4 16:25 admin-openrc.sh
-rw-r--r--  1 root root  268 Jul 10 15:55 admin-openrc-v3.sh
...
\>>> p.returncode
\>>> p.wait()
0
\>>> p.returncode

这里也可以使用 p = subprocess.Popen(['ls', '-cl']) 来创建子进程。

Popen 对象方法

  • poll(): 检查进程是否终止,如果终止返回 returncode,否则返回 None。
  • wait(timeout): 等待子进程终止。
  • communicate(input,timeout): 和子进程交互,发送和读取数据。
  • send_signal(singnal): 发送信号到子进程 。
  • terminate(): 停止子进程,也就是发送SIGTERM信号到子进程。
  • kill(): 杀死子进程。发送 SIGKILL 信号到子进程。

实例

import time
import subprocess

def cmd(command):
subp = subprocess.Popen(command,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8")
subp.wait(2)
if subp.poll() == 0:
print(subp.communicate()[1])
else:
print("失败")

cmd("java -version")
cmd("exit 1")

输出结果如下:

java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)

获取命令执行信息

#!/usr/bin/python3
#_*_coding:utf-8_*_

import subprocess
import sys


def run_cmd(cmd):
    p = subprocess.Popen(cmd,
                         stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         shell=True,
                         encoding='utf-8')
    stdout,stderr = p.communicate()
    if p.returncode != 0:
        return p.returncode,stderr
    return p.returncode,stdout


if __name__ == '__main__':
    cmd = ' '.join(sys.argv[1:])
    res = run_cmd(cmd)
    if res[0] != 0:
        print('未执行成功')
        print(res[1])
    else:
        print('执行结果: ')
        print(res[1])

批量测试主机ip的连通性

#!/usr/bin/python3
#_*_coding:utf-8_*_

import subprocess


with open('host.txt','r') as f:
    for ip in f.readlines():
        p = subprocess.Popen(f'ping -c 1 {ip}',shell=True,
                       stdin=subprocess.PIPE,
                       stdout=subprocess.PIPE,
                       stderr=subprocess.PIPE
                       )
        stdout,stderr = p.communicate()
        if p.returncode != 0:
            print(f'{ip.strip()}未通')
        else:
            print(f'{ip.strip()}可以ping通')
posted @   逃离这世界~  阅读(33)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示

目录导航