Python调用外部命令获取输出方法
在Python中执行外部命令并获取输出是常见的任务,`subprocess`模块提供了强大的功能。本文重点介绍如何使用`subprocess.run()`函数,它是Python 3.5+版本推荐的方法,通过设置`capture_output=True`捕获标准输出和标准错误,`text=True`返回字符串结果,`check=True`在命令失败时抛出异常。对于长时间运行的命令,推荐使用`subprocess.Popen()`实现非阻塞执行,并配合`communicate(timeout=...)`避免程序卡死。同时,文章强调了安全性,建议避免使用`shell=True`以防止注入攻击,推荐使用参数列表传递命令,并通过`env`和`cwd`控制子进程的环境变量和工作目录,确保Python脚本的健壮性和可靠性。
使用subprocess.run()是Python执行外部命令并捕获输出的推荐方法,它通过capture_output=True获取stdout和stderr,text=True返回字符串结果,check=True在命令失败时抛出异常;对于长时间运行的命令,应使用subprocess.Popen()实现非阻塞执行,配合communicate(timeout=...)避免程序卡死;安全方面需避免shell=True防止注入攻击,改用参数列表传递命令,并可通过env和cwd控制子进程环境与工作目录。

在Python里执行外部命令并捕获其输出,最直接也最推荐的方式是使用内置的subprocess模块。它提供了非常灵活和强大的功能来创建新的进程、连接到它们的输入/输出/错误管道,并获取它们的返回码。简单来说,如果你需要运行一个shell命令或者其他可执行程序,并且想拿到它打印到屏幕上的内容,subprocess.run()就是你的首选。它封装了许多细节,让这个过程变得既安全又方便。
解决方案
要执行一个外部命令并获取其输出,最核心的工具是Python的subprocess.run()函数。这个函数在Python 3.5+版本中被引入,旨在替代许多老旧的subprocess函数(比如call, check_call, check_output),提供了一个更统一、更现代的接口。
当你调用subprocess.run()时,你可以通过几个关键参数来控制其行为:
args: 这是你想要执行的命令。通常,为了安全起见,我们建议将其作为一个列表传递,其中第一个元素是命令本身,后续元素是其参数。例如,['ls', '-l', '/tmp']。如果你设置为shell=True,也可以传递一个字符串,但那样会有安全隐患,我们稍后会提到。capture_output=True: 这个参数告诉Python捕获外部命令的标准输出(stdout)和标准错误(stderr)。如果没有它,命令的输出会直接打印到你的控制台。text=True(或encoding='utf-8'): 当capture_output=True时,默认捕获的输出是字节串(bytes)。如果你希望直接得到字符串,可以使用text=True(Python 3.7+)或者指定一个encoding,比如encoding='utf-8'。check=True: 如果外部命令以非零退出码结束(通常表示命令执行失败),这个参数会让subprocess.run()抛出一个CalledProcessError异常。这对于错误处理非常有用。
下面是一个基本示例:
import subprocess
try:
# 执行 'ls -l' 命令,捕获输出并以文本形式返回
# check=True 会在命令执行失败时抛出异常
result = subprocess.run(
['ls', '-l'],
capture_output=True,
text=True,
check=True
)
print("命令执行成功!")
print("标准输出:")
print(result.stdout)
if result.stderr:
print("标准错误:")
print(result.stderr)
except subprocess.CalledProcessError as e:
print(f"命令执行失败,退出码: {e.returncode}")
print(f"错误输出: {e.stderr}")
print(f"标准输出 (如果存在): {e.stdout}")
except FileNotFoundError:
print("错误:命令未找到。请检查命令路径或环境变量。")
except Exception as e:
print(f"发生未知错误: {e}")
# 另一个例子:执行一个不存在的命令,看看 check=True 的效果
print("\n--- 尝试执行一个不存在的命令 ---")
try:
subprocess.run(
['nonexistent_command'],
capture_output=True,
text=True,
check=True
)
except subprocess.CalledProcessError as e:
print(f"正如预期,命令执行失败,退出码: {e.returncode}")
print(f"错误输出: {e.stderr.strip()}")
except FileNotFoundError:
print("命令 'nonexistent_command' 未找到。这是预期的行为。")subprocess.run()会返回一个CompletedProcess对象,这个对象包含了命令执行的详细信息:
returncode: 命令的退出码。0通常表示成功。stdout: 命令的标准输出(如果capture_output=True)。stderr: 命令的标准错误(如果capture_output=True)。
如何处理外部命令的错误输出和非零退出码?
处理外部命令的错误输出和非零退出码,是执行外部程序时一个不可避免,也是至关重要的环节。很多时候,我们不仅关心命令是否成功运行,更关心它失败的原因。
首先,subprocess.run()的check=True参数是处理非零退出码的利器。当外部命令以非零状态码退出时(比如一个grep命令没有找到匹配项,或者一个编译命令遇到了语法错误),check=True会立即抛出一个subprocess.CalledProcessError异常。这使得你可以在Python代码中集中处理这些“失败”的情况,而不是让程序默默地继续执行,可能带着不正确的结果。
捕获这个异常后,你可以访问异常对象e的属性:
e.returncode: 外部命令的退出码。e.stdout: 命令的标准输出(即使失败,也可能有部分输出)。e.stderr: 命令的标准错误输出。
这让你能够详细诊断问题。比如,一个编译命令失败了,你就可以把e.stderr打印出来,看到具体的编译错误信息。
至于错误输出(stderr),capture_output=True参数默认会将stdout和stderr都捕获到CompletedProcess对象的stdout和stderr属性中。如果你只想捕获stderr而不关心stdout,或者想对它们进行不同的处理,你可以使用stdout=subprocess.PIPE和stderr=subprocess.PIPE来更精细地控制。不过,对于大多数情况,capture_output=True已经足够了,它会把它们分开存储。
有时候,非零退出码并不一定意味着“错误”。例如,grep命令在没有找到匹配项时会返回1,这在脚本逻辑中可能是预期的行为,而不是需要抛异常的错误。在这种情况下,你可以选择不设置check=True,而是手动检查result.returncode:
import subprocess
command = ['grep', 'nonexistent_pattern', 'nonexistent_file.txt'] # 肯定会失败的命令
print("--- 不使用 check=True,手动检查退出码 ---")
result = subprocess.run(
command,
capture_output=True,
text=True,
check=False # 不抛出异常
)
if result.returncode != 0:
print(f"命令 '{' '.join(command)}' 执行失败,退出码: {result.returncode}")
print(f"错误信息:\n{result.stderr.strip()}")
# 这里你可以根据 returncode 的值做更细致的判断
# 比如,如果是 grep 的 1,可能只是没找到,而不是真正的错误
else:
print(f"命令 '{' '.join(command)}' 执行成功。")
print(f"输出:\n{result.stdout.strip()}")
# 考虑一个 grep 找到内容和没找到内容的场景
print("\n--- grep 示例 ---")
with open("temp_file.txt", "w") as f:
f.write("hello world\n")
f.write("python is great\n")
# 找到匹配项
grep_command_found = ['grep', 'python', 'temp_file.txt']
result_found = subprocess.run(grep_command_found, capture_output=True, text=True, check=False)
print(f"grep 'python' (找到): returncode={result_found.returncode}, stdout='{result_found.stdout.strip()}'")
# 未找到匹配项
grep_command_not_found = ['grep', 'java', 'temp_file.txt']
result_not_found = subprocess.run(grep_command_not_found, capture_output=True, text=True, check=False)
print(f"grep 'java' (未找到): returncode={result_not_found.returncode}, stdout='{result_not_found.stdout.strip()}', stderr='{result_not_found.stderr.strip()}'")
# 清理临时文件
import os
os.remove("temp_file.txt")这种手动检查的方式给了你更大的控制权,但同时也意味着你需要自己处理所有可能的错误路径,不像check=True那样能够快速失败并抛出异常。选择哪种方式,取决于你的具体需求和对外部命令行为的理解。
在执行长时间运行的外部命令时,如何避免程序阻塞?
subprocess.run()虽然好用,但它有一个特点:它是阻塞的。这意味着Python程序会暂停执行,直到外部命令完成并返回结果。对于那些需要瞬间完成的命令(比如ls、pwd),这通常不是问题。但如果你的外部命令需要运行几秒、几分钟甚至更长时间(例如,一个大型编译任务、数据处理脚本或网络请求),你的Python程序会一直等待,直到命令结束,这会造成用户界面卡顿、服务器无响应等问题。
为了避免程序阻塞,你需要使用subprocess.Popen()。Popen是subprocess模块的“底层”接口,它允许你启动一个子进程,然后立即返回,让你的Python程序可以继续执行其他任务。你可以把Popen想象成一个“启动器”,它只负责把命令扔出去,然后就不管了,让命令在后台自己跑。
使用Popen的基本流程是:
创建
Popen对象:这会启动子进程。process = subprocess.Popen( ['long_running_script.sh'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True # 如果需要文本输出 )注意,这里我们通常会明确指定
stdout=subprocess.PIPE和stderr=subprocess.PIPE,以便稍后能够捕获它们的输出。继续执行其他任务:你的Python程序可以做其他事情,例如更新UI、处理其他请求、或者启动另一个子进程。
等待和获取输出:当需要子进程的结果时,你可以使用
process.communicate()方法。这个方法会阻塞,直到子进程结束,然后返回一个包含stdout和stderr的元组。stdout, stderr = process.communicate(timeout=60) # 可以设置超时
communicate()的timeout参数非常有用,它能防止程序无限期地等待一个卡住的子进程。如果超时,它会抛出subprocess.TimeoutExpired异常。检查退出码:
process.returncode属性会告诉你子进程的退出码。在communicate()之后,这个属性会被设置。
这里有一个例子,模拟一个耗时命令:
import subprocess
import time
import os
# 创建一个模拟长时间运行的脚本
long_script_content = """
#!/bin/bash
echo "Starting long task..."
sleep 5
echo "Task finished."
exit 0
"""
with open("long_task.sh", "w") as f:
f.write(long_script_content)
os.chmod("long_task.sh", 0o755) # 赋予执行权限
print("--- 使用 Popen 启动长时间任务 ---")
start_time = time.time()
try:
# 启动子进程,不阻塞主程序
process = subprocess.Popen(
['./long_task.sh'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
print(f"主程序:子进程已启动,PID: {process.pid}。我将继续做其他事情...")
# 主程序可以在这里做一些其他工作
for i in range(3):
print(f"主程序:正在执行其他任务... ({i+1}秒)")
time.sleep(1)
print("主程序:现在等待子进程完成并获取输出...")
# 等待子进程完成并获取输出,设置超时为 10 秒
stdout, stderr = process.communicate(timeout=10)
end_time = time.time()
print(f"主程序:子进程已完成,耗时 {end_time - start_time:.2f} 秒。")
print(f"子进程退出码: {process.returncode}")
print(f"子进程标准输出:\n{stdout.strip()}")
if stderr:
print(f"子进程标准错误:\n{stderr.strip()}")
except subprocess.TimeoutExpired:
process.kill() # 超时时杀死子进程
stdout, stderr = process.communicate() # 再次communicate获取被杀死前的输出
print("主程序:子进程执行超时,已被终止。")
print(f"部分输出:\n{stdout.strip()}")
except Exception as e:
print(f"主程序:发生错误: {e}")
finally:
# 清理临时脚本
if os.path.exists("long_task.sh"):
os.remove("long_task.sh")如果你需要更高级的非阻塞操作,例如在子进程运行时实时读取其输出,或者同时管理多个子进程,你可能需要结合select模块或者asyncio库来异步地读取管道,但这超出了基础Popen的范畴,属于更复杂的并发编程。对于大多数非阻塞需求,Popen配合communicate()(带timeout)已经足够。
Python执行外部命令时,有哪些安全性和环境配置的考量?
执行外部命令不仅仅是运行起来那么简单,它还涉及到安全性和环境配置,这些往往是决定一个脚本健壮性和可靠性的关键因素。
安全性:shell=True的陷阱
首先要提的是shell=True这个参数。如果你在subprocess.run()或Popen()中设置了shell=True,那么你的命令会通过系统的shell来执行(在Linux上通常是/bin/sh,在Windows上是cmd.exe)。这听起来很方便,因为你可以直接传递一个字符串,里面包含管道符(|)、重定向符(>)等shell特性,比如subprocess.run("ls -l | grep .py", shell=True)。
然而,shell=True是一个巨大的安全隐患,尤其当你的命令中包含任何来自用户或其他不可信源的数据时。这被称为“shell注入”攻击。如果用户输入被直接拼接到命令字符串中,恶意用户可以通过注入额外的shell命令来执行任意操作。
例如:
假设你有一个命令是cmd = f"cat {filename}",如果用户输入filename是myfile.txt; rm -rf /,那么你的命令就变成了cat myfile.txt; rm -rf /,这会导致灾难性的后果。
最佳实践是:尽可能避免使用shell=True。
相反,将命令和其参数作为列表传递给subprocess函数。当shell=False(这是默认值)时,Python会直接执行命令,而不是通过shell。这意味着它不会解析shell的特殊字符,从而避免了shell注入的风险。
# 安全的方式:使用列表传递参数
subprocess.run(['ls', '-l', '/tmp'])
# 不安全的方式:避免在用户输入中直接使用 shell=True
# user_input = "malicious_file.txt; rm -rf /"
# subprocess.run(f"cat {user_input}", shell=True) # 极度危险!环境配置:env和cwd
外部命令的执行环境对结果有很大影响。subprocess模块提供了env和cwd参数,让你能够精确控制子进程的环境。
env参数:允许你为子进程设置特定的环境变量。默认情况下,子进程会继承父进程(你的Python脚本)的所有环境变量。但有时你可能需要修改或添加一些变量,例如设置PATH来确保命令能够被找到,或者设置特定的库路径。env参数接受一个字典,键值对就是环境变量名和值。import os my_env = os.environ.copy() # 复制当前环境是好习惯 my_env["MY_CUSTOM_VAR"] = "Hello From Python" my_env["PATH"] = "/usr/local/bin:" + my_env["PATH"] # 添加一个路径 # 运行一个会打印环境变量的命令 # 在 Linux/macOS 上: subprocess.run(['bash', '-c', 'echo $MY_CUSTOM_VAR && echo $PATH'], env=my_env, text=True) # 在 Windows 上: # subprocess.run(['cmd', '/c', 'echo %MY_CUSTOM_VAR% && echo %PATH%'], env=my_env, text=True)
cwd参数:指定子进程的当前工作目录(Current Working Directory)。很多命令的行为都依赖于它们是在哪个目录下执行的。例如,ls命令默认会列出当前目录的内容。如果你需要在一个特定的目录中运行命令,而不是Python脚本所在的目录,就可以使用cwd。import os # 假设 /tmp/test_dir 存在且里面有文件 if not os.path.exists("/tmp/test_dir"): os.makedirs("/tmp/test_dir") with open("/tmp/test_dir/file1.txt", "w") as f: f.write("test") print("--- 在不同工作目录执行 ls ---") # 在 Python 脚本当前目录执行 ls print("当前目录的 ls:") subprocess.run(['ls'], text=True) # 在 /tmp/test_dir 目录下执行 ls print("\n/tmp/test_dir 目录的 ls:") subprocess.run(['ls'], cwd="/tmp/test_dir", text=True) # 清理 os.remove("/tmp/test_dir/file1.txt") os.rmdir("/tmp/test_dir")
超时机制:timeout
对于任何可能长时间运行的外部命令,设置一个超时机制是至关重要的。一个卡住的命令可能会导致你的Python程序永久阻塞,或者消耗大量系统资源。subprocess.run()提供了timeout参数,可以在指定时间后自动终止子进程。
import subprocess
import time
print("--- 带有超时机制的命令 ---")
try:
# 尝试运行一个会持续 10 秒的命令,但只给它 3 秒时间
subprocess.run(
['sleep', '10'],
timeout=3,
check=True
)
print("命令成功完成(这不应该发生)")
except subprocess.TimeoutExpired:
print("命令因超时被终止。这是预期的。")
except subprocess.CalledProcessError as e:
print(f"命令失败,退出码: {e.returncode}")
except Exception as e:
print(f"发生未知错误: {e}")timeout参数在subprocess.Popen().communicate()方法中也可用,用法类似。这大大提高了程序的鲁棒性,防止外部命令的意外行为影响到整个Python应用。
综合来看,理解并恰当使用subprocess模块的这些参数,不仅能让你高效地执行外部命令,更能确保你的代码安全、稳定且易于维护。
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于文章的相关知识,也可关注golang学习网公众号。
Win8体验指数怎么查?详细步骤教程
- 上一篇
- Win8体验指数怎么查?详细步骤教程
- 下一篇
- 12306支付密码忘记怎么找回
-
- 文章 · python教程 | 6小时前 |
- Python如何重命名数据列名?columns教程
- 165浏览 收藏
-
- 文章 · python教程 | 7小时前 |
- 异步Python机器人如何非阻塞运行?
- 216浏览 收藏
-
- 文章 · python教程 | 7小时前 |
- Python排序忽略大小写技巧详解
- 325浏览 收藏
-
- 文章 · python教程 | 8小时前 |
- Python列表引用与复制技巧
- 300浏览 收藏
-
- 文章 · python教程 | 8小时前 | 数据处理 流处理 PythonAPI PyFlink ApacheFlink
- PyFlink是什么?Python与Flink结合解析
- 385浏览 收藏
-
- 文章 · python教程 | 9小时前 | sdk 邮件API requests库 smtplib Python邮件发送
- Python发送邮件API调用方法详解
- 165浏览 收藏
-
- 文章 · python教程 | 9小时前 |
- Pandasmerge_asof快速匹配最近时间数据
- 254浏览 收藏
-
- 文章 · python教程 | 9小时前 |
- 列表推导式与生成器表达式区别解析
- 427浏览 收藏
-
- 文章 · python教程 | 9小时前 |
- Pythonopen函数使用技巧详解
- 149浏览 收藏
-
- 文章 · python教程 | 9小时前 |
- Python合并多个列表的几种方法
- 190浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3193次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3405次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3436次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4543次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3814次使用
-
- Flask框架安装技巧:让你的开发更高效
- 2024-01-03 501浏览
-
- Django框架中的并发处理技巧
- 2024-01-22 501浏览
-
- 提升Python包下载速度的方法——正确配置pip的国内源
- 2024-01-17 501浏览
-
- Python与C++:哪个编程语言更适合初学者?
- 2024-03-25 501浏览
-
- 品牌建设技巧
- 2024-04-06 501浏览

