✨✨ 欢迎大家来到景天科技苑✨✨
🎈🎈 养成好习惯,先赞后看哦~🎈🎈
🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。所属的专栏:python综合应用,基础语法到高阶实战教学
景天的主页:景天科技苑
文章目录
- subprocess
- 一、subprocess模块概述
- 1.1 常用函数
- 1.2 subprocess.Popen类
- 二、subprocess.run()方法详解
- 2.1 基本用法
- 示例:执行ls命令
- 2.2 参数解读
- 三、subprocess.Popen()方法详解
- 3.1 基本用法
- 示例:执行ls命令并捕获输出
- 3.2 参数解读
- 3.3 进阶用法
- 示例:使用Popen的wait()方法
- 四、实际案例:使用subprocess执行系统命令并处理输出
- 4.1 案例背景
- 4.2 实现步骤
- 4.3 代码实现
- 4.4 注意事项
- 五、异步执行命令
- 5.1 使用`concurrent.futures.ThreadPoolExecutor`
- 六、 捕获并处理信号
- 七、替代方案
- 八、使用`subprocess.Popen`进行更复杂的交互
- 8.1 与子进程交互
- 8.2 捕获子进程的退出码
- 8.3 环境变量
- 8.4 使用shell特性(谨慎)
- 8.5 清理资源
- 九、跨平台兼容性
subprocess
Python的subprocess
模块是一个非常强大的工具,它允许用户启动新的进程,并与这些进程进行交互,获取其标准输入、标准输出、标准错误以及返回状态码等。这个模块提供了多种方式来执行外部命令和程序,并处理它们的输出和错误。在本文中,我们将通过实际案例详细介绍subprocess
模块的使用方法和各个方法中参数的解读。
一、subprocess模块概述
subprocess
模块是Python 2.4中新增的,旨在替换旧的如os.system()
、os.spawn*
等函数。它提供了更加灵活和强大的方式来生成新的进程,并与之交互。subprocess
模块的主要功能包括执行外部命令、捕获输出和错误信息、处理输入和输出管道、以及等待进程完成等。
1.1 常用函数
在subprocess
模块中,有几个常用的函数,它们分别是:
subprocess.run()
:Python 3.5及更高版本推荐使用,用于执行命令并等待其完成,返回一个CompletedProcess
实例。subprocess.Popen()
:提供了更多的灵活性,允许与进程进行交互,并不仅仅是等待它完成。subprocess.call()
、subprocess.check_call()
、subprocess.check_output()
:这些函数是对subprocess.Popen
的封装,提供了更简单的接口来执行命令并处理输出。
1.2 subprocess.Popen类
subprocess.Popen
是subprocess
模块中最重要的类,它用于创建新的进程,并允许我们与这个进程进行交互。Popen
的构造函数接受多个参数,这些参数定义了如何启动和管理子进程。
二、subprocess.run()方法详解
2.1 基本用法
subprocess.run()
函数是Python 3.5及更高版本中推荐使用的执行外部命令的方式。它执行指定的命令,并等待命令完成,然后返回一个CompletedProcess
实例。
示例:执行ls命令
import subprocess# 执行ls命令,并捕获输出
result = subprocess.run(["ls", "-l"], stdout=subprocess.PIPE, text=True)
print(result.stdout)
在这个例子中,subprocess.run()
函数接受一个包含命令及其参数的列表["ls", "-l"]
,stdout=subprocess.PIPE
表示捕获标准输出,text=True
表示将输出作为文本处理。
查看运行结果
2.2 参数解读
subprocess.run()是subprocess模块中一个常用的函数,也是官方推荐的方法,它用于运行命令并等待其完成。
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, encoding=None, errors=None, text=None, cwd=None, env=None, universal_newlines=None)
subprocess.run()
函数的参数非常丰富,下面是一些常用的参数及其解释:
args
:要执行的命令,可以是字符串或字符串列表。如果是字符串,且shell=True
,则命令将通过shell执行。stdin
、stdout
、stderr
:分别表示子进程的标准输入、标准输出、标准错误。可以设置为subprocess.PIPE
、subprocess.DEVNULL
、已存在的文件描述符、已打开的文件对象或None
,我们使用时,默认为None就行。input
:作为子进程的输入发送的数据(默认为None)。shell
:如果为True
,则通过shell执行命令。注意,这可能会带来安全风险,因为它允许执行shell命令,包括文件名通配符等。timeout
:设置命令的超时时间(秒)。如果命令执行时间超过这个时间,则抛出TimeoutExpired
异常。check
:如果为True
,且命令执行后返回状态码不是0,则抛出CalledProcessError
异常。universal_newlines
(在Python 3.6及以后版本中被弃用,建议使用text
):如果为True
,则输入和输出都将以文本形式处理,而不是字节序列。encoding
:用于指定文本模式的编码方式。如果text=True
,则此参数指定编码方式。errors
:用于指定文本模式下编码或解码错误的处理方式。text
: text=True表示将输出作为文本处理。默认是None
三、subprocess.Popen()方法详解
3.1 基本用法
subprocess.Popen()
函数提供了比subprocess.run()
更多的灵活性,它允许我们与进程进行更复杂的交互。
示例:执行ls命令并捕获输出
import subprocess# 执行ls命令
process = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)# 等待进程完成,并获取输出
out, err = process.communicate()
print("标准输出:", out)
print("标准错误:", err)
在这个例子中,subprocess.Popen()
用于启动进程,并通过stdout=subprocess.PIPE
和stderr=subprocess.PIPE
捕获标准输出和标准错误。然后,使用process.communicate()
方法等待进程完成,并获取其输出和错误。
3.2 参数解读
subprocess.Popen()是subprocess模块中用于创建子进程的函数之一。它提供了更灵活的控制和处理子进程的能力。
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)
最新版
subprocess.Popen()
的构造函数接受多个参数,这些参数定义了如何启动和管理子进程。以下是一些关键的参数及其解释:
args
:要执行的命令,可以是字符串或字符串列表。如果是字符串,且shell=True
,则命令将通过shell执行。推荐使用字符串列表,以避免shell注入等安全问题。bufsize
:指定缓冲区的大小。0表示无缓冲,1表示行缓冲,其他正整数表示缓冲区大小(字节),负数表示使用系统默认值。executable
:用于替换子进程中的shell来执行指定的命令。在Unix上,默认是/bin/sh
。在Windows上,可执行文件通常从args
列表中直接获取。stdin
、stdout
、stderr
:与subprocess.run()
相同,分别表示子进程的标准输入、标准输出、标准错误。preexec_fn
:在子进程执行前调用的函数。注意,这个函数只在Unix系统上有效,并且它只应该在子进程的安全环境中调用(即,在子进程启动后,但在执行任何来自args
的命令之前)。close_fds
:在Unix系统上,如果为True
,则除了stdin
、stdout
、stderr
之外的所有文件描述符都将在子进程中关闭。在Windows上,此参数被忽略。shell
:与subprocess.run()
相同,如果为True
,则通过shell执行命令。cwd
:设置子进程的当前工作目录。env
:用于子进程的环境变量字典。如果为None
,则使用父进程的环境变量。universal_newlines
(在Python 3.6及以后版本中被弃用,建议使用text
):如果为True
,则输入和输出都将以文本形式处理,而不是字节序列。startupinfo
和creationflags
(仅限Windows):这两个参数用于控制子进程的创建方式,如窗口样式、继承的句柄等。restore_signals
(仅限Unix):如果为True
,则所有Python信号处理器都将被恢复到它们在父进程中的状态。start_new_session
(仅限Unix):如果为True
,则子进程将成为一个新的会话领导者,并且不会继承父进程的会话和进程组。pass_fds
(仅限Unix):一个包含要传递给子进程的额外文件描述符的序列。encoding
和errors
:与subprocess.run()
相同,用于指定文本模式下的编码方式和错误处理方式。
3.3 进阶用法
subprocess.Popen()
提供了比subprocess.run()
更多的灵活性,允许我们进行更复杂的进程管理。例如,我们可以使用Popen
对象的poll()
方法来检查子进程是否已结束,使用wait()
方法来等待子进程结束并获取其退出码,或者使用send_signal()
方法来向子进程发送信号。
示例:使用Popen的wait()方法
import subprocess# 执行ls命令
process = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)# 等待进程完成
exit_code = process.wait()
print(f"进程退出码: {exit_code}")# 注意:此时已经不能通过communicate()获取输出了,因为它已经被wait()等待完成
# 如果需要输出,应该在wait()之前调用communicate()
在这个例子中,我们使用wait()
方法等待进程完成,并获取其退出码。但是,请注意,一旦调用了wait()
或communicate()
(后者也会等待进程完成),就不能再次调用communicate()
来获取输出了,因为输出缓冲区已经被读取并清空。
四、实际案例:使用subprocess执行系统命令并处理输出
4.1 案例背景
假设我们需要编写一个Python脚本,该脚本需要执行系统命令grep
来搜索文件中的内容,并处理搜索结果。我们将使用subprocess.run()
来执行这个命令,并捕获其输出。
4.2 实现步骤
-
确定命令和参数:在这个案例中,我们将使用
grep
命令来搜索文件example.txt
中包含特定字符串target
的行。 -
执行命令并捕获输出:使用
subprocess.run()
执行命令,并通过stdout=subprocess.PIPE
和text=True
捕获输出。 -
处理输出:将捕获的输出(作为字符串)进行进一步处理,如打印到控制台或保存到文件中。
4.3 代码实现
下面是一个Python脚本的示例,它使用subprocess.run()
来执行grep
命令,并处理其输出。
import subprocess# 定义要搜索的字符串和文件名
search_string = "target"
file_name = "example.txt"# 构建grep命令的字符串列表
# 注意:为了安全起见,不要直接将用户输入直接拼接到命令中,这里我们只是使用固定值
grep_command = ["grep", search_string, file_name]# 执行grep命令并捕获输出
try:result = subprocess.run(grep_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)# check=True 会在命令执行失败(即非零退出码)时抛出 CalledProcessError 异常# 打印搜索到的行if result.stdout:print("搜索到的行:")print(result.stdout)else:print(f"在文件 {file_name} 中没有找到包含 '{search_string}' 的行。")except subprocess.CalledProcessError as e:# 处理异常情况,比如grep命令不存在等print(f"执行命令时发生错误: {e}")except Exception as e:# 处理其他可能的异常print(f"发生错误: {e}")
4.4 注意事项
-
安全性:在上述示例中,由于
search_string
和file_name
是固定值,因此不需要担心shell注入等安全问题。但是,如果你打算将这些值替换为用户输入,你需要特别小心,避免直接将用户输入拼接到命令字符串中。一个更安全的方法是使用列表来构建命令参数,如本例所示。 -
错误处理:在调用
subprocess.run()
时,可以通过设置check=True
来自动检查命令的退出码。如果命令执行失败(即退出码非零),则会抛出subprocess.CalledProcessError
异常。你可以通过捕获这个异常来优雅地处理错误情况。 -
输出处理:在上述示例中,我们通过检查
result.stdout
来查看命令的输出。如果命令执行成功但没有输出(即result.stdout
为空字符串),我们可以认为搜索未找到任何匹配项。如果命令执行失败,并且你希望捕获错误输出,可以通过检查result.stderr
来实现。 -
文本模式与字节模式:在Python 3中,默认情况下,
subprocess.run()
会以字节模式运行,即stdout
和stderr
都是字节序列。如果你希望以文本模式处理输出,可以设置text=True
,这样stdout
和stderr
就会以字符串形式返回。同时,你也可以通过encoding
参数来指定文本的编码方式。
通过上述案例,你可以看到subprocess
模块在Python中执行外部命令和处理输出时的强大功能。无论是在脚本编写、自动化任务还是数据分析等领域,subprocess
模块都是一个非常有用的工具。
当然,我们可以继续深入探讨subprocess
模块的一些高级用法和最佳实践。
五、异步执行命令
如果你需要同时执行多个命令,并且希望它们并行运行而不是依次执行,你可以考虑使用asyncio
模块与subprocess
结合,或者使用concurrent.futures
模块中的ThreadPoolExecutor
。但是,请注意,由于subprocess
模块本身是同步的,直接并行执行多个subprocess.run()
或subprocess.Popen()
调用将需要多线程或多进程的支持。
5.1 使用concurrent.futures.ThreadPoolExecutor
import concurrent.futures
import subprocessdef run_command(command):try:result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)return result.stdoutexcept subprocess.CalledProcessError as e:return f"Error: {e}"# 命令列表
commands = [["ls", "-l"], ["grep", "some_pattern", "some_file.txt"]]with concurrent.futures.ThreadPoolExecutor() as executor:# 启动所有命令的异步执行future_to_command = {executor.submit(run_command, command): command for command in commands}for future in concurrent.futures.as_completed(future_to_command):command = future_to_command[future]try:# 获取命令的输出data = future.result()except Exception as exc:# 处理异常data = str(type(exc))print(f'{command} 输出: {data}')
六、 捕获并处理信号
在Unix系统上,你可以使用signal
模块与subprocess.Popen
结合来捕获和处理信号。这对于需要优雅地终止子进程或响应系统事件(如用户中断)的应用程序特别有用。
import signal
import subprocessdef signal_handler(sig, frame):print(f'You pressed Ctrl+C! Signal: {sig}')# 尝试优雅地终止子进程process.terminate()# 设置信号处理程序
signal.signal(signal.SIGINT, signal_handler)# 启动子进程
process = subprocess.Popen(["some_long_running_command"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)# 主程序的其他部分...# 注意:在这个简化的例子中,我们没有等待子进程完成。
# 在实际应用中,你可能需要调用 process.wait() 或其他机制来同步子进程的状态。
七、替代方案
虽然subprocess
模块功能强大且灵活,但在某些情况下,你可能需要考虑其他替代方案,特别是对于那些需要更复杂交互或跨平台兼容性的应用程序。
- pexpect:用于自动化交互式应用程序的Python模块,它可以模拟用户输入和响应。
- paramiko:一个用于SSH2协议的Python实现,允许你远程执行命令、上传/下载文件等。
- fabric(现已并入
invoke
):一个用于远程或本地执行命令的Python库,提供了更高级别的抽象。
八、使用subprocess.Popen
进行更复杂的交互
subprocess.Popen
是subprocess
模块中最强大的类,它允许你与子进程进行更复杂的交互。通过Popen
,你可以设置各种输入输出流(stdin, stdout, stderr),并可以在子进程运行期间读取其输出或向其发送输入。
8.1 与子进程交互
import subprocess# 启动子进程,设置stdin, stdout, stderr
# 注意:这里我们使用text=True以获取文本模式的输出
process = subprocess.Popen(["python", "-c", "for line in sys.stdin: print('Echo:', line.strip())"],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,universal_newlines=True # Python 3.7之前的版本可能需要这个参数
)# 向子进程发送输入
try:# 发送多行输入for line in ["Hello, subprocess!", "Another line.", "Goodbye."]:process.stdin.write(line + "\n")process.stdin.flush() # 确保数据被发送# 读取子进程的输出output = process.stdout.readline().strip()print(f"Received: {output}")finally:# 关闭stdin,确保子进程可以结束process.stdin.close()# 等待子进程完成process.wait()# 获取并打印可能的错误输出if process.stderr:print(f"Stderr: {process.stderr.read()}")
注意:上述代码中的universal_newlines=True
参数在Python 3.7及以后的版本中已被弃用,并被text=True
参数所取代。我同时包含了universal_newlines
以提醒旧版本Python的用户。
8.2 捕获子进程的退出码
子进程的退出码可以通过Popen
对象的returncode
属性获取,但在子进程完成之前,这个属性是None
。你可以使用wait()
方法来等待子进程完成并获取退出码。
process = subprocess.Popen(["some_command"])
process.wait() # 等待子进程完成
exit_code = process.returncode # 获取退出码
print(f"Exit code: {exit_code}")
或者,你可以使用run()
方法的returncode
属性,它会在命令执行后立即提供退出码(如果命令已经执行完成的话)。
8.3 环境变量
在启动子进程时,可以指定一个不同的环境变量字典。这对于需要在不同环境设置下运行命令的场景非常有用。
env = {"PATH": "/usr/bin:/bin","MY_VAR": "some_value"
}
process = subprocess.Popen(["some_command"], env=env)
8.4 使用shell特性(谨慎)
虽然你可以通过将命令作为单个字符串并设置shell=True
来利用shell的特性(如管道、文件通配符等),但这通常不推荐,因为它会使你的代码更容易受到shell注入攻击。如果你确实需要这样做,请确保你完全控制传递给shell的字符串,或者使用其他方法来避免这种风险。
# 谨慎使用,特别是当命令字符串包含用户输入时
output = subprocess.run("ls -l | grep some_pattern", shell=True, text=True, capture_output=True)
8.5 清理资源
当使用Popen
时,确保在不再需要时关闭与子进程相关联的文件描述符(stdin, stdout, stderr)并等待子进程完成。这可以通过调用close()
方法和wait()
方法来实现,或者通过使用with
语句(如果可用)来自动管理。
九、跨平台兼容性
当编写跨平台的脚本时,请注意不同操作系统之间路径分隔符、环境变量和shell特性的差异。尽量使用Python的库函数(如os.path.join()
)和模块(如shutil
、os
)来避免硬编码的路径和命令,以增加代码的可移植性。
通过遵循这些指导原则和最佳实践,你可以更有效地使用subprocess
模块来执行外部命令和处理输出,同时保持代码的安全性、可读性和可维护性。