各位观众,晚上好!我是今晚的讲师,很高兴和大家一起聊聊Python subprocess
模块这三个重量级选手:run()
、popen()
和call()
。它们就像厨房里的三把刀,各有千秋,用对了能让你料理各种系统命令得心应手。
今天,我们不搞虚的,直接上干货,用最接地气的方式,把这三个方法扒个底朝天,让大家以后遇到需要执行系统命令的情况,不再抓瞎。
开胃小菜:subprocess
模块是干啥的?
简单来说,subprocess
模块就是Python连接操作系统shell的桥梁。有了它,你就可以在Python程序里像直接在命令行里敲命令一样,执行各种系统命令,比如ls
、ping
、mkdir
等等。这对于自动化运维、脚本编写、甚至简单的程序交互都非常有用。
第一道主菜:subprocess.run()
– 现代化的瑞士军刀
run()
方法是Python 3.5之后引入的,可以说是官方推荐的用法。它就像一把瑞士军刀,功能强大且使用方便。它会等待命令执行完毕,并返回一个CompletedProcess
对象,包含了命令执行的所有信息,例如返回值、标准输出、标准错误等等。
-
阻塞式执行:
run()
会阻塞程序的执行,直到命令执行完毕。这意味着你的Python代码会停在那里,等待命令执行完成,才能继续往下走。 -
基本用法:
import subprocess result = subprocess.run(['ls', '-l'], capture_output=True, text=True) if result.returncode == 0: print("命令执行成功!") print("标准输出:", result.stdout) print("标准错误:", result.stderr) else: print("命令执行失败!") print("错误码:", result.returncode) print("标准错误:", result.stderr)
这段代码执行了
ls -l
命令,并将结果捕获到result
对象中。capture_output=True
告诉run()
捕获标准输出和标准错误,text=True
告诉run()
以文本模式处理输出,这样你就可以直接用字符串的方式访问result.stdout
。 -
常用参数:
args
: 要执行的命令,可以是一个字符串,也可以是一个列表。建议使用列表,可以避免shell注入的风险。capture_output
: 是否捕获标准输出和标准错误。默认为False
,表示不捕获。text
: 是否以文本模式处理标准输出和标准错误。默认为False
,表示以字节流模式处理。check
: 是否检查命令的返回值。如果为True
,并且命令的返回值不为0,则会抛出一个subprocess.CalledProcessError
异常。timeout
: 命令执行的超时时间,单位为秒。超过这个时间,命令会被强制终止,并抛出一个subprocess.TimeoutExpired
异常。shell
: 是否使用shell执行命令。默认为False
。如果为True
,则会将args
作为一个字符串传递给shell执行。不建议使用shell=True
,除非你真的需要shell的特性,比如管道和通配符。
-
check=True
的威力:import subprocess try: result = subprocess.run(['不存在的命令'], capture_output=True, text=True, check=True) except subprocess.CalledProcessError as e: print("命令执行失败!") print("错误码:", e.returncode) print("标准错误:", e.stderr)
这段代码执行了一个不存在的命令,由于
check=True
,所以run()
会抛出一个CalledProcessError
异常,我们可以通过try...except
语句来捕获这个异常,并进行处理。 -
超时控制:
import subprocess try: result = subprocess.run(['sleep', '5'], timeout=2, capture_output=True, text=True) except subprocess.TimeoutExpired as e: print("命令执行超时!") print("超时时间:", e.timeout) print("命令:", e.cmd)
这段代码执行了一个会休眠5秒的命令,但是设置了超时时间为2秒,所以
run()
会抛出一个TimeoutExpired
异常。 -
使用管道:
虽然不推荐使用
shell=True
,但在某些情况下,使用管道可以简化代码。import subprocess result = subprocess.run('ls -l | grep "myfile.txt"', capture_output=True, text=True, shell=True) print(result.stdout)
这段代码使用了管道,将
ls -l
的输出传递给grep "myfile.txt"
命令进行过滤。注意,使用shell=True
时,需要格外小心shell注入的风险。
第二道主菜:subprocess.Popen()
– 掌控进程的精细大师
Popen()
方法是subprocess
模块的核心,其他方法都是基于它实现的。它允许你更精细地控制子进程的执行,例如可以实时获取子进程的输出,可以向子进程发送输入,等等。
-
非阻塞式执行:
Popen()
启动一个子进程后,会立即返回一个Popen
对象,而不会等待命令执行完毕。这意味着你的Python代码可以继续往下执行,而子进程在后台运行。 -
基本用法:
import subprocess process = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) stdout, stderr = process.communicate() print("标准输出:", stdout) print("标准错误:", stderr) print("返回值:", process.returncode)
这段代码使用
Popen()
启动了ls -l
命令,并将标准输出和标准错误重定向到管道中。然后,使用process.communicate()
方法从管道中读取输出,并获取命令的返回值。 -
常用参数:
args
: 要执行的命令,和run()
一样,建议使用列表。stdin
: 子进程的标准输入。可以是一个文件对象,也可以是一个管道。stdout
: 子进程的标准输出。可以是一个文件对象,也可以是一个管道。stderr
: 子进程的标准错误。可以是一个文件对象,也可以是一个管道。shell
: 是否使用shell执行命令,和run()
一样,不建议使用。cwd
: 子进程的工作目录。env
: 子进程的环境变量。
-
实时获取输出:
import subprocess import time process = subprocess.Popen(['ping', 'www.baidu.com'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) while True: line = process.stdout.readline() if line: print(line.strip()) if process.poll() is not None: #检查子进程是否结束 break time.sleep(0.1) print("返回值:", process.returncode)
这段代码使用
Popen()
启动了ping www.baidu.com
命令,并实时获取输出。process.stdout.readline()
方法会阻塞,直到读取到一行数据或者文件结束。process.poll()
方法会检查子进程是否结束,如果结束则返回子进程的返回值,否则返回None
。 -
向子进程发送输入:
import subprocess process = subprocess.Popen(['python'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) process.stdin.write("print('Hello from child process!')n") process.stdin.flush() process.stdin.close() # 必须关闭输入流,否则子进程会一直等待输入 stdout, stderr = process.communicate() print("标准输出:", stdout) print("标准错误:", stderr) print("返回值:", process.returncode)
这段代码使用
Popen()
启动了一个Python解释器,并通过process.stdin.write()
方法向子进程发送Python代码。注意,必须调用process.stdin.close()
方法关闭输入流,否则子进程会一直等待输入。 -
wait()
方法:process.wait()
方法会等待子进程执行完毕,并返回子进程的返回值。它和run()
方法一样,是阻塞式的。import subprocess process = subprocess.Popen(['sleep', '5']) print("开始等待...") returncode = process.wait() print("等待结束,返回值:", returncode)
-
kill()
和terminate()
方法:process.kill()
方法会向子进程发送SIGKILL
信号,强制终止子进程。process.terminate()
方法会向子进程发送SIGTERM
信号,请求子进程正常终止。import subprocess import time process = subprocess.Popen(['sleep', '10']) time.sleep(2) process.terminate() # 或者 process.kill() print("已发送终止信号") returncode = process.wait() print("返回值:", returncode)
第三道主菜:subprocess.call()
– 简单粗暴,但有时也够用
call()
方法是subprocess
模块中最简单的方法。它会执行命令,并等待命令执行完毕,然后返回命令的返回值。
-
阻塞式执行:
call()
也会阻塞程序的执行,直到命令执行完毕。 -
基本用法:
import subprocess returncode = subprocess.call(['ls', '-l']) print("返回值:", returncode)
这段代码执行了
ls -l
命令,并将返回值打印出来。 -
重定向输出:
call()
方法可以通过stdout
和stderr
参数将标准输出和标准错误重定向到文件对象。import subprocess with open('output.txt', 'w') as f: returncode = subprocess.call(['ls', '-l'], stdout=f) print("返回值:", returncode)
这段代码将
ls -l
命令的标准输出重定向到output.txt
文件中。 -
call()
的局限性:call()
方法的功能比较简单,无法捕获标准输出和标准错误,也无法实时获取输出。因此,在大多数情况下,建议使用run()
或Popen()
方法。
餐后甜点:总结与选择
功能 | subprocess.run() |
subprocess.Popen() |
subprocess.call() |
---|---|---|---|
执行方式 | 阻塞式 | 非阻塞式(需要配合communicate() 或wait() 等方法使用) |
阻塞式 |
返回值 | CompletedProcess 对象 (包含返回值, 标准输出, 标准错误等) |
Popen 对象 (可以控制子进程, 获取标准输入/输出/错误) |
返回命令的返回值 |
标准输出/错误捕获 | 支持 (通过 capture_output=True ) |
支持 (通过 stdout=subprocess.PIPE , stderr=subprocess.PIPE ) |
支持重定向到文件 (通过 stdout=file , stderr=file ) |
实时输出 | 不支持 | 支持 (通过循环读取 stdout 和 stderr ) |
不支持 |
超时控制 | 支持 (通过 timeout 参数) |
需要手动实现 (例如使用 process.wait(timeout=...) 并捕获 TimeoutExpired 异常) |
不支持 |
使用场景 | 简单命令执行, 需要获取完整结果, 并对结果进行处理 | 需要更精细地控制子进程, 例如实时获取输出, 向子进程发送输入, 或者需要非阻塞式执行命令.需要进行复杂的进程间通信或控制。 | 简单命令执行, 只需要返回值, 不需要获取输出 |
推荐程度 | 强烈推荐 (Python 3.5+) | 推荐 (需要更精细的控制时) | 不推荐 (功能较弱, 建议使用 run() 或 Popen() ) |
选择建议:
- 如果只是想简单地执行一个命令,并获取它的返回值和输出,那么
run()
方法是你的首选。 - 如果需要更精细地控制子进程的执行,例如实时获取输出,向子进程发送输入,或者需要非阻塞式执行命令,那么
Popen()
方法是你的不二之选。 - 如果只是想简单地执行一个命令,并获取它的返回值,那么
call()
方法也可以凑合用用,但是不推荐。
注意事项:
- 尽量避免使用
shell=True
,除非你真的需要shell的特性。 - 在使用
Popen()
方法时,一定要记得关闭输入流,否则子进程可能会一直等待输入。 - 在使用管道时,要注意缓冲区的大小,避免死锁。
好了,今天的讲座就到这里。希望大家能够掌握subprocess
模块的这三个方法,并在实际工作中灵活运用。记住,没有最好的方法,只有最适合的方法。根据不同的场景选择不同的方法,才能让你事半功倍。
谢谢大家!下次再见!