Python中subprocess模块:轻松调用外部程序与命令

liftword12小时前技术文章3

大家好!在Python的世界里,subprocess模块提供了强大且灵活的方式来创建和管理子进程,让Python 程序能够调用外部程序、执行系统命令。

一、subprocess模块简介

subprocess是Python的标准库,它的设计目标是替代旧有的os.system、os.popen等模块,提供更统一、更强大的子进程管理功能。使用subprocess可以启动外部程序、获取程序执行结果、管理进程的输入输出和错误信息,在自动化脚本、系统管理、程序集成等场景中发挥重要作用。

二、核心函数介绍与案例

1. subprocess.run():执行命令并等待完成

subprocess.run()是subprocess模块中最常用的函数,用于执行指定的命令,并等待命令执行完毕。它接受args参数(命令及其参数),以及input等可选参数,返回一个CompletedProcess对,包含命令执行结果信息。

1). popenargs:指定要执行的命令及参数

popenargs是subprocess.run()中最核心的参数,必须传入,它用于指定要执行的外部命令及其参数。args通常以列表形式传递,列表的第一个元素是命令本身,后续元素为命令的参数。

案例:执行简单命令

import subprocess

# 执行ls命令,获取当前目录下的文件列表
result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
if result.returncode == 0:
    print("命令执行成功,输出如下:")
    print(result.stdout)
else:
    print(f"命令执行失败,错误信息:{result.stderr}")

说明:代码中使用subprocess.run()执行ls -l命令,capture_output=True表示捕获命令的标准输出和标准错误输出,text=True则以文本形式返回输出结果。通过检查returncode判断命令是否执行成功。

2). stdin、stdout、stderr:控制输入输出和错误流

  • stdin:指定子进程的标准输入,可以是文件对象、字节串或字符串(当text=True时)。如果不指定,子进程的标准输入将继承父进程的标准输入。
  • stdout:指定子进程的标准输出,常见取值为subprocess.PIPE(用于捕获输出)、文件对象或None(默认继承父进程的标准输出)。
  • stderr:指定子进程的标准错误输出,取值和用法与stdout类似。

案例:捕获命令的输出和错误信息

import subprocess
# 执行一个错误的命令,捕获输出和错误
result = subprocess.run(['ls', 'nonexistent_file'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
print("标准输出:", result.stdout)
print("标准错误:", result.stderr)

#输出为:
标准输出: 
标准错误: ls: nonexistent_file: No such file or directory

3). capture_output:快速捕获输出和错误

capture_output是一个布尔值参数,当设置为True时,相当于同时将stdout和stderr设置为subprocess.PIPE,并自动捕获子进程的标准输出和标准错误。

案例:使用capture_output简化代码

import subprocess
# 执行一个错误的命令,捕获输出和错误
result = subprocess.run(['ls', 'nonexistent_file'],capture_output=True, text=True)
print("标准输出:", result.stdout)
print("标准错误:", result.stderr)

4). shell:使用系统shell执行命令

shell是一个布尔值参数,默认为False。当shell=True时,命令会通过系统的shell(如bash)执行,此时执行命令可以是字符串形式,如下“ls nonexistent_file”。

import subprocess
# 执行一个错误的命令,捕获输出和错误
result = subprocess.run('ls nonexistent_file',shell=True,capture_output=True, text=True)
print("标准输出:", result.stdout)
print("标准错误:", result.stderr)

但这种方式可能会引入安全问题,例如:

import subprocess
user_input = "; rm -rf /" # 恶意输入示例
# 危险操作!可能会删除系统根目录
subprocess.run(f'echo {user_input}', shell=True)

因此,除非必要,否则不建议使用shell=True,若确实需要,可以考虑使用shlex.quote对输入进行转义。

5). cwd:指定子进程的工作目录

cwd参数用于指定子进程执行时的工作目录。如果不指定,子进程将在父进程的当前工作目录下执行。

案例:在指定目录下执行命令

import subprocess
# 在/tmp目录下执行ls命令
result = subprocess.run(['ls'], cwd='/tmp', capture_output=True, text=True)
print(result.stdout)

6). timeout:设置命令执行的超时时间

timeout参数用于设置子进程执行的最长时间(单位为秒)。如果子进程在指定时间内未执行完毕,将引发subprocess.TimeoutExpired异常。

案例:设置命令执行超时

import subprocess

try:
    # 执行ping命令,设置超时时间为5秒
    result = subprocess.run(['ping', 'www.baidu.com'], timeout=5, capture_output=True, text=True)
    print(result.stdout)
except subprocess.TimeoutExpired:
    print("命令执行超时!")

7). check:检查命令执行状态

check是一个布尔值参数,默认为False。当check=True时,如果子进程的返回码不为0,将引发
subprocess.CalledProcessError异常。

案例:检查命令是否执行成功

import subprocess
try:
 # 执行一个可能失败的命令,开启check检查
 result = subprocess.run(['ls','nonexistent_file'], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError:
 print("命令执行失败!")

2. subprocess.Popen():创建并管理子进程

subprocess.Popen()函数用于创建一个新的子进程,并返回一个Popen对象,通过该对象可以控制子进程的执行、获取输出、与子进程进行交互

subprocess.Popen()是subprocess模块的底层接口,与run()的主要区别在于:

阻塞 vs 非阻塞:run()是阻塞式的(等待命令执行完毕),而Popen()是非阻塞式的(立即返回进程对象)。

灵活性:Popen()提供更底层的控制,适合需要实时交互或复杂进程管理的场景。

2.1 参数介绍

1).args:命令及参数(必填)

与run()相同,指定要执行的命令。推荐使用列表形式,避免安全风险。

import subprocess

# 正确写法:列表形式
p = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE)

# 危险写法(shell=True):
# p = subprocess.Popen('ls -l', shell=True)

2).stdin/stdout/stderr:控制输入输出

与run()类似,但更灵活,支持实时处理流数据。

常见取值

  • subprocess.PIPE:创建管道,用于捕获输出或提供输入。
  • 文件对象:将输出 / 输入重定向到文件。
  • None(默认):继承父进程。

案例:实时获取命令输出

import subprocess

# 执行命令并实时获取输出
p = subprocess.Popen(['ping', 'www.baidu.com'], stdout=subprocess.PIPE, text=True)

# 逐行读取输出
for line in iter(p.stdout.readline, ''):
    print(line.strip())

p.wait()  # 等待进程结束

3).shell:通过shell执行命令(慎用)

与run()的shell参数功能相同,但风险更高。仅在必要时使用,并确保输入经过严格过滤。

# 安全风险示例(切勿在生产环境使用)
user_input = "example.txt; rm -rf /"
subprocess.Popen(f'cat {user_input}', shell=True)

4).cwd:指定工作目录

与run()的cwd参数功能相同。

import subprocess

# 在/tmp目录下执行命令
p = subprocess.Popen(['ls'], cwd='/tmp', stdout=subprocess.PIPE)
print(p.stdout.read().decode())

5).env:自定义环境变量

覆盖或添加子进程的环境变量。

import subprocess
import os

# 自定义环境变量
my_env = {**os.environ, 'MY_VAR': 'custom_value'}

p = subprocess.Popen(['printenv', 'MY_VAR'], env=my_env, stdout=subprocess.PIPE)
print(p.stdout.read().decode())  # 输出: custom_value

6).bufsize:控制缓冲区大小

  • -1(默认):使用系统默认缓冲区大小。
  • 0:无缓冲(仅适用于stdin、stdout、stderr为管道时)。
  • 1:行缓冲(仅在text=True时有效)。
  • >1:指定缓冲区大小(字节)。
# 无缓冲模式,实时获取输出
p = subprocess.Popen(['ls'], stdout=subprocess.PIPE, bufsize=0)

7).start_new_session(Windows/Linux)

在新会话中启动进程。

# 在Windows或Linux上创建新会话
p = subprocess.Popen(['python', 'script.py'], start_new_session=True)

2.2 一些应用场景
1). 实时监控命令输出

import subprocess

# 实时监控日志文件
p = subprocess.Popen(['tail', '-f', '/var/log/syslog'], stdout=subprocess.PIPE, text=True)

try:
    for line in iter(p.stdout.readline, ''):
        print(f"日志: {line.strip()}")
except KeyboardInterrupt:
    p.terminate()  # 按Ctrl+C终止监控

2). 并行执行多个命令

import subprocess
import time

# 并行执行多个命令
commands = [
    ['sleep', '2'],
    ['ls', '-l'],
    ['echo', 'Hello']
]

processes = [subprocess.Popen(cmd) for cmd in commands]

# 等待所有进程结束
for p in processes:
    p.wait()

print("所有命令执行完毕")

3). 复杂交互场景(如 FTP 客户端)

import subprocess

# 启动交互式FTP客户端
p = subprocess.Popen(['ftp', 'ftp.example.com'], 
                    stdin=subprocess.PIPE, 
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True)

# 发送登录命令
p.stdin.write('user\n')
p.stdin.write('password\n')
p.stdin.flush()

# 读取响应
output = p.stdout.read(1024)

2.3 Popen对象的其他方法

方法

描述

p.wait(timeout)

等待进程结束并返回返回码,可设置超时(超时抛出TimeoutExpired)。

p.communicate(input=None, timeout=None)

与进程交互(发送输入、获取输出),等待进程结束。

p.send_signal(signal)

向进程发送信号(如signal.SIGTERM)。

p.terminate()

终止进程(发送 SIGTERM 信号)。

p.kill()

强制杀死进程(发送 SIGKILL 信号)。

p.poll()

检查进程是否结束,返回返回码(未结束返回None)。

3. subprocess.check_output():执行命令并返回输出

subprocess.check_output()函数执行指定命令,若命令执行成功,返回命令的标准输出;若执行失败(返回码非 0),则抛出CalledProcessError异常。

案例:获取系统磁盘空间信息

import subprocess

try:
    result = subprocess.check_output(['df', '-h'], text=True)
    print("磁盘空间信息:")
    print(result)
except subprocess.CalledProcessError as e:
    print(f"命令执行失败,错误码:{e.returncode},错误信息:{e.output}")

说明:代码执行df -h命令获取磁盘空间信息,若命令执行失败,捕获异常并输出错误信息。

subprocess模块赋予了Python与外部世界交互的强大能力,通过掌握这些核心函数,我们可以在 Python开发中实现更丰富的功能,无论是自动化运维、数据采集,还是程序集成,subprocess都能成为你的得力助手。

如果在使用过程中有任何疑问或新发现,欢迎在评论区分享交流!

相关文章

每天一个Python库:lxml全面实战指南,爬虫解析速度翻倍

你是否遇到过:BeautifulSoup 解析太慢?re 提取数据太容易误匹配?抓数据时 HTML 太乱根本不好搞?本期,我们用 lxml + XPath 一把梭!学习本来就不是一蹴而就的事,不过只要...

Python学不会来打我(37)yield关键字详解,一篇讲清所有知识点

在Python中,yield 关键字 是生成器(Generator)的核心组成部分。它使得函数可以“暂停执行”,并在后续调用时“继续执行”,从而实现了一种轻量级的协程机制。本文将详细讲解 yield...

python入门到脱坑—字符串的切片

在 Python 中,字符串切片(String Slicing)是一种强大的操作,可以灵活地提取子字符串。以下是 字符串切片的详细指南,包含基础语法、高级技巧和实际应用场景:1. 基础切片语法text...

Python学不会来打我(81)yield关键字的作用总结

上一篇文章我们介绍了yield创建的生成器,yield除了创建生成器之外,还有其他的作用,今天我们就分享yield关键字的其他几个作用!#python##python教程##python自学##...

Python 中的前缀删除操作全指南

1. 字符串前缀删除1.1 使用内置方法Python 提供了几种内置方法来处理字符串前缀的删除:# 1. 使用 removeprefix() 方法 (Python 3.9+) text = "...