在Python 3.5以上版本中,官方推荐使用subprocess模块执行系统命令,它替代了旧有的os.system和os.popen。本文将详细对比subprocess.run、subprocess.Popen、shutil、os.system和os.popen五种方式,并提供可落地的代码示例和参数说明。
一、快速对比表
下面列出各方法的核心特点:
- subprocess.run (推荐指数★★★★★):官方推荐,可捕获输出、状态码、错误信息,支持超时、输入、环境变量,适用于绝大多数场景。
- subprocess.Popen (★★★★☆):最灵活,支持异步执行、管道通信、重定向,适用于高级进程控制,但代码量较大。
- shutil相关函数 (★★★★☆):跨平台文件操作的抽象,内部调用系统命令但提供Pythonic接口,适用于文件/目录复制、移动、删除、压缩解压等。
- os.system (★★☆☆☆):一行代码执行命令,但无法获取输出,返回值是平台相关状态码,依赖系统shell,仅适用于快速确认命令是否成功。
- os.popen (★☆☆☆☆):可读取命令输出,但功能单一且已被淘汰,仅用于旧代码维护。
二、最佳实践:subprocess.run()
subprocess.run是Python 3.5新增的高阶函数,参数以列表形式传递可避免shell注入。- import subprocess
- # 示例1:执行简单命令,不捕获输出
- result = subprocess.run(["ls", "-l"])
- print(f"命令返回码: {result.returncode}")
- # 示例2:捕获标准输出和错误
- result = subprocess.run(["echo", "Hello from subprocess"], capture_output=True, text=True)
- print(f"标准输出: {result.stdout}")
- print(f"标准错误: {result.stderr}")
- print(f"返回码: {result.returncode}")
- # 示例3:使用shell=True执行shell命令(注意安全风险)
- result = subprocess.run("ls -l *.txt | wc -l", shell=True, capture_output=True, text=True)
- print(f"当前目录下txt文件行数: {result.stdout.strip()}")
- # 示例4:设置超时和错误处理
- try:
- result = subprocess.run(["sleep", "5"], timeout=3, capture_output=True, text=True)
- except subprocess.TimeoutExpired:
- print("命令执行超时,已被终止!")
- except subprocess.CalledProcessError as e:
- print(f"命令执行失败: {e.stderr}")
复制代码
三、高级控制:subprocess.Popen()
当需要双向交互、实时读取输出或管理多个进程时,使用Popen。- import subprocess, time
- # 示例1:启动后台进程并等待
- process = subprocess.Popen(["ping", "-c", "4", "example.com"], stdout=subprocess.PIPE, text=True)
- return_code = process.wait()
- output, _ = process.communicate()
- print(output)
- # 示例2:实时读取进程输出
- process = subprocess.Popen(["tail", "-f", "/var/log/syslog"], stdout=subprocess.PIPE, text=True)
- try:
- for line in iter(process.stdout.readline, ''):
- print(f"实时日志: {line.strip()}")
- time.sleep(0.1)
- except KeyboardInterrupt:
- process.terminate()
- process.wait()
- # 示例3:管道连接多个命令,模拟 `ls -l | grep .py`
- ls_process = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
- grep_process = subprocess.Popen(["grep", ".py"], stdin=ls_process.stdout, stdout=subprocess.PIPE, text=True)
- ls_process.stdout.close()
- output = grep_process.communicate()[0]
- print(f"找到的.py文件:\n{output}")
复制代码
四、文件操作专用:shutil模块
对于跨平台文件或目录操作,应优先使用shutil而非直接调用cp、rm等系统命令。- import shutil
- # 复制文件(保留元数据)
- shutil.copy2('source.txt', 'dest.txt')
- # 递归复制目录
- shutil.copytree('source_dir', 'dest_dir')
- # 移动文件/目录
- shutil.move('old_name.txt', 'new_name.txt')
- # 删除整个目录树(相当于rm -rf)
- shutil.rmtree('directory_to_delete')
- # 归档压缩与解压
- shutil.make_archive('backup', 'zip', 'my_folder')
- shutil.unpack_archive('backup.zip', 'extract_folder')
复制代码
五、遗留方法:os.system()与os.popen()
os.system直接将命令字符串交给shell执行,无法捕获输出,返回值是状态码(0表示成功)。os.popen可以读取或写入命令流,但已被subprocess取代。新代码中应避免使用。- import os
- # os.system示例
- ret = os.system('echo Hello World')
- if ret == 0:
- print("命令执行成功")
- else:
- print(f"命令失败,返回码: {ret}")
- # os.popen示例(过时写法)
- output = os.popen('ls -l').read()
- print(output)
复制代码
六、总结与最佳实践
1. 95%的场景使用subprocess.run,以列表形式传递参数,避免shell=True。
2. 需要异步、实时交互或复杂管道时,使用subprocess.Popen。
3. 文件/目录/归档操作,用shutil代替直接调用系统命令,既跨平台又安全。
4. 避免在新项目中使用os.system和os.popen,它们功能弱、依赖shell且存在安全隐患。
5. 若必须使用shell=True(如命令中包含通配符或管道),请确保参数来自可信来源,否则存在shell注入风险。
通过遵循以上指南,你可以高效、安全地在Python脚本中执行系统命令。 |