找回密码
 会员注册
查看: 26|回复: 0

pythonPython如何调用外部命令,subprocess模块的详细解读以及应用实战

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
69864
发表于 2024-9-5 12:39:06 | 显示全部楼层 |阅读模式
✨✨欢迎大家来到景天科技苑✨✨🎈🎈养成好习惯,先赞后看哦~🎈🎈🏆作者简介:景天科技苑🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。🏆《博客》:Python全栈,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。所属的专栏:python综合应用,基础语法到高阶实战教学景天的主页:景天科技苑文章目录subprocess一、subprocess模块概述1.1常用函数1.2subprocess.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清理资源九、跨平台兼容性subprocessPython的subprocess模块是一个非常强大的工具,它允许用户启动新的进程,并与这些进程进行交互,获取其标准输入、标准输出、标准错误以及返回状态码等。这个模块提供了多种方式来执行外部命令和程序,并处理它们的输出和错误。在本文中,我们将通过实际案例详细介绍subprocess模块的使用方法和各个方法中参数的解读。一、subprocess模块概述subprocess模块是Python2.4中新增的,旨在替换旧的如os.system()、os.spawn*等函数。它提供了更加灵活和强大的方式来生成新的进程,并与之交互。subprocess模块的主要功能包括执行外部命令、捕获输出和错误信息、处理输入和输出管道、以及等待进程完成等。1.1常用函数在subprocess模块中,有几个常用的函数,它们分别是:subprocess.run():Python3.5及更高版本推荐使用,用于执行命令并等待其完成,返回一个CompletedProcess实例。subprocess.Popen():提供了更多的灵活性,允许与进程进行交互,并不仅仅是等待它完成。subprocess.call()、subprocess.check_call()、subprocess.check_output():这些函数是对subprocess.Popen的封装,提供了更简单的接口来执行命令并处理输出。1.2subprocess.Popen类subprocess.Popen是subprocess模块中最重要的类,它用于创建新的进程,并允许我们与这个进程进行交互。Popen的构造函数接受多个参数,这些参数定义了如何启动和管理子进程。二、subprocess.run()方法详解2.1基本用法subprocess.run()函数是Python3.5及更高版本中推荐使用的执行外部命令的方式。它执行指定的命令,并等待命令完成,然后返回一个CompletedProcess实例。示例:执行ls命令importsubprocess#执行ls命令,并捕获输出result=subprocess.run(["ls","-l"],stdout=subprocess.PIPE,text=True)print(result.stdout)12345在这个例子中,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(在Python3.6及以后版本中被弃用,建议使用text):如果为True,则输入和输出都将以文本形式处理,而不是字节序列。encoding:用于指定文本模式的编码方式。如果text=True,则此参数指定编码方式。errors:用于指定文本模式下编码或解码错误的处理方式。text:text=True表示将输出作为文本处理。默认是None三、subprocess.Popen()方法详解3.1基本用法subprocess.Popen()函数提供了比subprocess.run()更多的灵活性,它允许我们与进程进行更复杂的交互。示例:执行ls命令并捕获输出importsubprocess#执行ls命令process=subprocess.Popen(["ls","-l"],stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)#等待进程完成,并获取输出out,err=process.communicate()print("标准输出:",out)print("标准错误:",err)123456789在这个例子中,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(在Python3.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()方法importsubprocess#执行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()1234567891011在这个例子中,我们使用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命令,并处理其输出。importsubprocess#定义要搜索的字符串和文件名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异常#打印搜索到的行ifresult.stdout:print("搜索到的行:")print(result.stdout)else:print(f"在文件{file_name}中没有找到包含'{search_string}'的行。")exceptsubprocess.CalledProcessErrorase:#处理异常情况,比如grep命令不存在等print(f"执行命令时发生错误:{e}")exceptExceptionase:#处理其他可能的异常print(f"发生错误:{e}")12345678910111213141516171819202122232425262728294.4注意事项安全性:在上述示例中,由于search_string和file_name是固定值,因此不需要担心shell注入等安全问题。但是,如果你打算将这些值替换为用户输入,你需要特别小心,避免直接将用户输入拼接到命令字符串中。一个更安全的方法是使用列表来构建命令参数,如本例所示。错误处理:在调用subprocess.run()时,可以通过设置check=True来自动检查命令的退出码。如果命令执行失败(即退出码非零),则会抛出subprocess.CalledProcessError异常。你可以通过捕获这个异常来优雅地处理错误情况。输出处理:在上述示例中,我们通过检查result.stdout来查看命令的输出。如果命令执行成功但没有输出(即result.stdout为空字符串),我们可以认为搜索未找到任何匹配项。如果命令执行失败,并且你希望捕获错误输出,可以通过检查result.stderr来实现。文本模式与字节模式:在Python3中,默认情况下,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.ThreadPoolExecutorimportconcurrent.futuresimportsubprocessdefrun_command(command):try:result=subprocess.run(command,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,check=True)returnresult.stdoutexceptsubprocess.CalledProcessErrorase:returnf"Error:{e}"#命令列表commands=[["ls","-l"],["grep","some_pattern","some_file.txt"]]withconcurrent.futures.ThreadPoolExecutor()asexecutor:#启动所有命令的异步执行future_to_command={executor.submit(run_command,command):commandforcommandincommands}forfutureinconcurrent.futures.as_completed(future_to_command):command=future_to_command[future]try:#获取命令的输出data=future.result()exceptExceptionasexc:#处理异常data=str(type(exc))print(f'{command}输出:{data}')1234567891011121314151617181920212223242526六、捕获并处理信号在Unix系统上,你可以使用signal模块与subprocess.Popen结合来捕获和处理信号。这对于需要优雅地终止子进程或响应系统事件(如用户中断)的应用程序特别有用。importsignalimportsubprocessdefsignal_handler(sig,frame):print(f'YoupressedCtrl+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()或其他机制来同步子进程的状态。123456789101112131415161718七、替代方案虽然subprocess模块功能强大且灵活,但在某些情况下,你可能需要考虑其他替代方案,特别是对于那些需要更复杂交互或跨平台兼容性的应用程序。pexpect:用于自动化交互式应用程序的Python模块,它可以模拟用户输入和响应。paramiko:一个用于SSH2协议的Python实现,允许你远程执行命令、上传/下载文件等。fabric(现已并入invoke):一个用于远程或本地执行命令的Python库,提供了更高级别的抽象。八、使用subprocess.Popen进行更复杂的交互subprocess.Popen是subprocess模块中最强大的类,它允许你与子进程进行更复杂的交互。通过Popen,你可以设置各种输入输出流(stdin,stdout,stderr),并可以在子进程运行期间读取其输出或向其发送输入。8.1与子进程交互importsubprocess#启动子进程,设置stdin,stdout,stderr#注意:这里我们使用text=True以获取文本模式的输出process=subprocess.Popen(["python","-c","forlineinsys.stdin:print('Echo:',line.strip())"],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,universal_newlines=True#Python3.7之前的版本可能需要这个参数)#向子进程发送输入try:#发送多行输入forlinein["Hello,subprocess!","Anotherline.","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()#获取并打印可能的错误输出ifprocess.stderr:print(f"Stderr:{process.stderr.read()}")12345678910111213141516171819202122232425262728293031323334注意:上述代码中的universal_newlines=True参数在Python3.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"Exitcode:{exit_code}")1234或者,你可以使用run()方法的returncode属性,它会在命令执行后立即提供退出码(如果命令已经执行完成的话)。8.3环境变量在启动子进程时,可以指定一个不同的环境变量字典。这对于需要在不同环境设置下运行命令的场景非常有用。env={"PATH":"/usr/bin:/bin","MY_VAR":"some_value"}process=subprocess.Popen(["some_command"],env=env)123458.4使用shell特性(谨慎)虽然你可以通过将命令作为单个字符串并设置shell=True来利用shell的特性(如管道、文件通配符等),但这通常不推荐,因为它会使你的代码更容易受到shell注入攻击。如果你确实需要这样做,请确保你完全控制传递给shell的字符串,或者使用其他方法来避免这种风险。#谨慎使用,特别是当命令字符串包含用户输入时output=subprocess.run("ls-l|grepsome_pattern",shell=True,text=True,capture_output=True)128.5清理资源当使用Popen时,确保在不再需要时关闭与子进程相关联的文件描述符(stdin,stdout,stderr)并等待子进程完成。这可以通过调用close()方法和wait()方法来实现,或者通过使用with语句(如果可用)来自动管理。九、跨平台兼容性当编写跨平台的脚本时,请注意不同操作系统之间路径分隔符、环境变量和shell特性的差异。尽量使用Python的库函数(如os.path.join())和模块(如shutil、os)来避免硬编码的路径和命令,以增加代码的可移植性。通过遵循这些指导原则和最佳实践,你可以更有效地使用subprocess模块来执行外部命令和处理输出,同时保持代码的安全性、可读性和可维护性。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-7 06:05 , Processed in 0.453141 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表