Python模块之subprocess
subprocess 是 Python 中执行操作系统级别的命令的模块,所谓系级级别的命令就是如ls /etc/user ifconfig 等和操作系统有关的命令。
subprocess 创建子进程来执行相关命令,并连接它们的输入、输出和错误管道,获取它们的返回状态。
subprocess 来源
Subprocess模块开发之前,标准库已有大量用于执行系统级别命令的的方法,如os.system、os.spawn等。但是略显混乱使开发者难以抉择,因此subprocess的目的是打造一个统一模块来替换之前执行系统界别命令的方法。
所以 推荐使用subprocess替代了一些老的方法,比如:os.system、os.spawn*等。
模块常用函数
函数清单总览
Subprocess 模块推荐使用run方法替换低版本方法,如果想要更加精细的控制可以使用Popen方法。所以本教程中重点介绍run和Popen方法。
subprocess.run()
函数签名
简单使用
默认情况下,子进程会继承父进程的设置,会将输出显示在终端上
如果命令没有输出则不会打印输出信息
获取状态码
returncode 是subprocess的返回码
参数介绍
args:要执行的命令,可以是字符串形式或由命令及其参数组成的列表。例如,['ls', '-l'] 或 'ls -l'。 input:允许将字节或字符串传递给子进程的标准输入(stdin)。 stdin:子进程的标准输入。默认为None,可以是以下三个参数: subprocess.PIPE 创建一个管道,允许与子进程进行通信 subprocess.DEVNULL 特殊的文件对象,可以将其用于丢弃子进程的输出 一个打开的文件对象,将内容写入文件 stdout: 同 stdin stderr: 同 stdin capture_output :这个参数控制是否捕获外部命令的标准输出(stdout)和标准错误(stderr)。如果将其设置为True,run()函数将返回一个CompletedProcess对象,该对象具有stdout和stderr属性,分别存储了命令的标准输出和标准错误输出。如果设置为False,标准输出和标准错误将被发送到控制台。默认为False。 shell:指定是否通过shell来执行命令。如果为True,命令将在shell中执行;如果为False,则直接调用可执行文件。默认为False。 cwd:设置子进程的工作目录。默认为None,表示使用当前工作目录。 timeout:设置子进程的超时时间(秒)。如果子进程在指定的时间内没有运行完成,则会引发TimeoutExpired异常。 check:设置是否检查子进程的返回码。如果为True,并且子进程的返回码不为零,则会引发CalledProcessError异常。 encoding:该参数指定输出结果的字符编码。默认情况下,它是None,表示使用原始的字节数据。如果提供了有效的编码名称(如"utf-8"、"gbk"等),run()函数将自动将输出解码为字符串。 errors:该参数定义在解码输出时如何处理编码错误。它与Python的str.decode()函数的相同参数含义相匹配。常用的值包括"strict" (默认值,抛出异常)、"ignore" (忽略错误字符) 和 "replace" (用替代字符代替错误字符)。 text:指定是否将输出结果以文本形式返回。如果为True,则结果以字符串形式返回,同时input或者stdin参数也需要输入String;如果为False,则返回字节流。默认为False。 env:该参数允许您为子进程指定环境变量。它可以接受一个字典类型的对象,其中键是环境变量的名称,值是环境变量的值。通过设置env参数,您可以在子进程中使用特定的环境变量。 universal_newlines: 该参数影响的是输入与输出的数据格式,比如它的值默认为False,此时stdout和stderr的输出是字节序列;当该参数的值设置为True时,stdout和stderr的输出是字符串。
args 执行命令
args传入的是要执行的系统命令,可以接收两种方法:字符串或列表。
- 使用列表形式subprocess.run(["ls", "-al"])
- 使用字符串形式 subprocess.run("ls -al", shell=True)。使用字符串形式必须设置参数shell=True
import subprocess subprocess.run(["ls", "-al", "/Users/ljk/Documents/code/daily_dev"]) subprocess.run("ls -al /Users/ljk/Documents/code/daily_dev", shell=True)
默认情况下,命令的输出是直接打印到控制台上的。
stdin、stdout、sterr 设置命令输出输入的对象
这三个值是用来设置标准输入,标准输出,标准错误的。默认情况下,子进程会继承父进程的设置,会将输出显示在控制台上,除此之外也可以设置成如下三个值:
- subprocess.PIPE 创建一个管道,允许与子进程进行通信
- subprocess.DEVNULL 特殊的文件对象,可以将其用于丢弃子进程的输出
- 一个打开的文件对象,将内容写入文件
以studout为例子,验证这三个输出选项。
将命令输出保存到管道
import subprocess res = subprocess.run(["ls", "-al", "/Users/ljk/Documents/code/daily_dev"], stdout=subprocess.PIPE) print(res.returncode) print(res.stdout)
命令输出不再打印到控制台上,而是保存到对象里,通过对象的stdout获取到。此时命令输出结果是字节串格式的。可以通过设置text=True,将命令输出以文本形式保存。
命令输出保存到文件中
可以将命令的输出保存到一个文件中,stdout传入一个打开的文件对象即可。
import subprocess with open("a.txt", "a+") as f: res = subprocess.run(["ls", "-al", "/Users/ljk/Documents/code/daily_dev"], stdout=f, text=True) print(res.returncode) print(res.stdout) >>> 0 None
此时在目录下生成了a.txt文件,里面保存的是命令的输出结果
capture_output 捕获控制台输出
捕获命令输出。默认为false,所有的命令输出都打印到控制台。设置为true,所有命令的输出都被捕获保存到返回对象中。
cwd 设置命令执行的目录
设置子进程的工作目录。默认为None,表示使用当前工作目录
import subprocess res = subprocess.run("pwd", shell=True, cwd="/Users/ljk/Documents/code/") print(res.returncode)
如果脚本需要在特定的目录中执行,可以设置该参数
timeout 设置命令超执行时时间
当一些命令有时间上的要求,可以设置命令执行的超时时间。如果命令在指定的时间内没有运行完成,则会引发TimeoutExpired异常。
执行的命令是先睡眠10s,然后执行ls。设置的超时时间是5秒,所以执行的第5s就抛出timeout错误。
check 返回码非1抛出错误
检查子进程的返回码。如果为True,并且子进程的返回码不为零,则会引发CalledProcessError异常。以下代码做了一组对比,ls一个不存在目录,设置check=True的会抛出异常。
encoding、text 设置输出结果的格式
encoding 用于设置命令输出的编码格式。 默认情况下,它是None,表示使用原始的字节数据。如果提供了有效的编码名称,如"utf-8"、"gbk",将自动将输出解码为字符串。示例演示encoding=True
text 参数是用于设置命令输出的格式。命令输出默认是字节串,text=True表示输出格式为字符串。和encoding=True 基本等价。
res = subprocess.run("ls -al /Users/ljk/Documents/code/daily_dev", shell=True, capture_output=True) print(res.returncode) print(res.stdout)
返回对象
subprocess.run()函数返回值是一个CompletedProcess类的实例,subprocess.completedPorcess类是Python 3.5以上才存在的。它表示的是一个已结束进程的状态信息,它所包含的属性和方法如下:
args: 用于加载该进程的参数,这可能是一个列表或一个字符串。
returncode: 子进程的退出状态码。通常情况下,退出状态码为0则表示进程成功运行了;一个负值-N表示这个子进程被信号N终止了。
stdout: 从子进程捕获的stdout。这通常是一个字节串。如果设置了encoding或text参数,返回就是字符串。
stderr: 从子进程捕获的stderr。它的值与stdout一样,是一个字节序列或一个字符串。如果stderr没有被捕获的话,它的值就为None。
check_returncode(): 如果returncode是一个非0值,则该方法会抛出一个CalledProcessError异常。
示例:
import subprocess res = subprocess.run("ls -al /home/ljk/Videos", shell=True) print("args:", res.args) print("returncode:", res.returncode) print("stdout:", res.stdout) print("stderr:", res.stderr) print("returncode():", res.check_returncode())
subprocess.Popen()
popen是一个功能更强大的方法,而run是它的一个简化版。如果run函数不能满足功能的要求,可以尝试功能更多的popen方法。
除了方法的多少之外,run和popen最大的区别在于:run方法是阻塞调用,会一直等待命令执行完成或失败;popen是非阻塞调用,执行之后立刻返回,结果通过返回对象获取。
popen函数签名:
简单使用
和run一样执行命令
import subprocess subprocess.Popen("ls -al /Users/ljk/Documents/code/daily_dev", shell=True)
执行阻塞命令
遇到阻塞命令也会直接返回,返回是一个对象。可以通过对象获取命令执行的结果。
参数介绍
注意:因为run是popen的一个简化版本,所以run拥有的函数popen也拥有。这里就不再重复说明了。
bufsize:定义了子进程的缓冲大小。可选参数,默认为-1,表示使用系统默认的缓冲大小。 executable:指定要执行的程序路径。如果未提供该值,则通过PATH环境变量来确定可执行文件的位置。 preexec_fn:指定在子进程启动之前将要执行的函数。该函数将在fork()调用成功,但exec()调用之前被调用。 close_fds:指定是否关闭所有文件描述符。默认为False。 start_new_session(仅 POSIX):如果该参数设置为True,则在启动子进程时创建一个新的进程会话。默认为False。 pass_fds(仅 POSIX):通过这个参数传递一个文件描述符集合,这些文件描述符将保持打开状态并传递给子进程。默认为None。 startupinfo:一个可选的subprocess.STARTUPINFO对象,用于指定子进程的启动信息,如窗口大小、窗口标题等。默认为None。 creationflags:用于指定子进程的创建标志,控制子进程的各种行为。可以使用subprocess.CREATE_NEW_CONSOLE、subprocess.CREATE_NEW_PROCESS_GROUP等常量进行设置。默认为0。 restore_signals(仅 POSIX):用于确定是否在子进程中恢复信号处理程序的默认行为。默认为True。 group(仅 POSIX): 如果 group 不为 None,则 setregid() 系统调用将于子进程执行之前在下级进程中进行。 如果所提供的值为一个字符串,将通过 grp.getgrnam() 来查找它,并将使用 gr_gid 中的值。 如果该值为一个整数,它将被原样传递。 (POSIX 专属) extra_groups(仅 POSIX): 如果 extra_groups 不为 None,则 setgroups() 系统调用将于子进程之前在下级进程中进行。 在 extra_groups 中提供的字符串将通过 grp.getgrnam() 来查找,并将使用 gr_gid 中的值。 整数值将被原样传递。 user(仅 POSIX): 如果 user 不为 None,则 setreuid() 系统调用将于子进程执行之前在下级进程中进行。 如果所提供的值为一个字符串,将通过 pwd.getpwnam() 来查找它,并将使用 pw_uid 中的值。 如果该值为一个整数,它将被原样传递。 (POSIX 专属)
Popen类的方法与参数介绍
communicate(input=None, timeout=None): 与子进程进行交互,发送输入并获取输出结果。可以在参数input中指定要发送给子进程的输入内容。该方法会阻塞当前进程,直到子进程完成并返回输出结果。可选的timeout参数用于设置超时时间。 poll(): 检查子进程是否已经退出,如果已退出则返回退出状态码,否则返回None。 wait(timeout=None): 等待子进程完成并返回退出状态码。可选的timeout参数用于设置超时时间。 terminate(): 向子进程发送终止信号。这通常是优雅地终止子进程。 kill(): 强制终止子进程。 send_signal(signal): 向子进程发送信号,其中signal参数表示要发送的信号类型,如SIGINT、SIGTERM等。
communicate 获取命令输出
发送输入并获取输出结果。可以在参数input中指定要发送给子进程的输入内容。该方法会阻塞当前进程,直到子进程完成并返回输出结果。函数返回一个元组: (stdoutdata , stderrdata )
import subprocess res = subprocess.Popen("sleep 3 && ls -al", shell=True) print(res.communicate())
该方法和run函数行为一致,将非阻塞调用变成阻塞调用。
subprocess.Popen().communicate() 等价于 subprocess.run()
poll 检查子进程
Poll 检查子进程是否已经退出,如果已退出则返回退出状态码,否则返回None。
在执行命令之后立刻用poll检查发现返回None,此时因为子进程还没有退出。程序睡眠4s之后命令已经退出,再次执行poll返回了状态码。在两次打印中间是命令的标准输出。
检查命令执行状态并获取返回值
wait 用于等待命令执行完成
等待子进程完成并返回退出状态码。可选的timeout参数用于设置超时时间。