0

0

python如何执行一个外部命令并获取输出_python执行外部命令并捕获输出的技巧

冰火之心

冰火之心

发布时间:2025-09-15 22:38:01

|

988人浏览过

|

来源于php中文网

原创

使用subprocess.run()是Python执行外部命令并捕获输出的推荐方法,它通过capture_output=True获取stdout和stderr,text=True返回字符串结果,check=True在命令失败时抛出异常;对于长时间运行的命令,应使用subprocess.Popen()实现非阻塞执行,配合communicate(timeout=...)避免程序卡死;安全方面需避免shell=True防止注入攻击,改用参数列表传递命令,并可通过env和cwd控制子进程环境与工作目录。

python如何执行一个外部命令并获取输出_python执行外部命令并捕获输出的技巧

在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
    异常。这对于错误处理非常有用。

下面是一个基本示例:

立即学习Python免费学习笔记(深入)”;

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
的基本流程是:

  1. 创建

    Popen
    对象:这会启动子进程。

    process = subprocess.Popen(
        ['long_running_script.sh'],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True # 如果需要文本输出
    )

    注意,这里我们通常会明确指定

    stdout=subprocess.PIPE
    stderr=subprocess.PIPE
    ,以便稍后能够捕获它们的输出。

  2. 继续执行其他任务:你的Python程序可以做其他事情,例如更新UI、处理其他请求、或者启动另一个子进程。

  3. 等待和获取输出:当需要子进程的结果时,你可以使用

    process.communicate()
    方法。这个方法会阻塞,直到子进程结束,然后返回一个包含
    stdout
    stderr
    的元组。

    Napkin AI
    Napkin AI

    Napkin AI 可以将您的文本转换为图表、流程图、信息图、思维导图视觉效果,以便快速有效地分享您的想法。

    下载
    stdout, stderr = process.communicate(timeout=60) # 可以设置超时

    communicate()
    timeout
    参数非常有用,它能防止程序无限期地等待一个卡住的子进程。如果超时,它会抛出
    subprocess.TimeoutExpired
    异常。

  4. 检查退出码

    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
模块的这些参数,不仅能让你高效地执行外部命令,更能确保你的代码安全、稳定且易于维护。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

761

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1570

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

651

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

1229

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

1205

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

193

2025.07.29

c++字符串相关教程
c++字符串相关教程

本专题整合了c++字符串相关教程,阅读专题下面的文章了解更多详细内容。

131

2025.08.07

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

49

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 10.7万人学习

Git 教程
Git 教程

共21课时 | 4.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号