前言:脚本里的「系统命令调用」刚需
作为Python初学者、运维入门同学,我们写脚本时总会遇到一个高频需求:在Python代码里执行系统命令 —— 比如执行 ls 查看目录、df -h 检查磁盘、ping 测试网络连通性、mkdir 创建文件夹,甚至执行Shell脚本、批量执行Linux命令。
最开始大家接触到的,大概率是 os.system() 这个简单的方法,它上手快、代码少,能满足最基础的命令执行需求。但用着用着就会发现各种「痛点」:拿不到命令执行的结果、判断不了命令执行成功还是失败、处理不了带管道/空格的复杂命令、无法控制子进程的输入输出...
而 subprocess 模块,就是Python官方为了解决这些痛点而生的「终极方案」,它被称为「运维脚本的瑞士军刀」—— 功能全面、灵活可控、安全稳定,是Python3推荐的标准系统命令调用模块,几乎能完美替代 os.system、os.popen 等老旧方法。
这篇文章我们从零开始,先讲透 os.system 和 subprocess 的基础用法,再对比两者的核心差异,最后分享趣味性+实用性拉满的最佳实践,全部是运维工作中能直接套用的干货,新手也能轻松看懂、直接上手!
一、先学基础:os.system 用法(简单但够用一时)
os.system 是什么?
os.system() 是Python内置 os 模块的一个方法,作用是在Python中执行一条系统命令,它的设计理念就是「极简」,没有多余的参数,入门门槛为0。
核心语法(一行搞定)
import os# 语法:os.system("需要执行的系统命令")os.system("系统命令")
实操案例(Linux/Windows通用)
import os# 案例1:执行简单命令,查看当前目录文件(Linux/macOS)os.system("ls -l")# Windows对应命令# os.system("dir")# 案例2:执行系统操作 - 创建文件夹os.system("mkdir test_dir")# 案例3:执行网络测试命令os.system("ping -c 3 www.baidu.com") # Linux/macOS# os.system("ping -n 3 www.baidu.com") # Windows# 案例4:执行Shell脚本os.system("sh test.sh")
os.system 的返回值(新手必踩坑)
重点记住:os.system() 的返回值,不是命令执行的结果,而是「命令的执行状态码」
- 状态码 非0 → 命令执行失败(比如命令写错、权限不足)
示例:
import os# 执行正确的命令res1 = os.system("ls")print(f"正确命令的返回值:{res1}") # 输出:0# 执行错误的命令res2 = os.system("ls_not_exist")print(f"错误命令的返回值:{res2}") # 输出:32512(Linux)/ 1(Windows)
os.system 的优缺点(直白总结)
优点:极致简单
缺点:短板太多,痛点密集(运维必遇)
- 拿不到命令的输出结果:比如执行
df -h 想获取磁盘使用率,os.system 只会把结果打印到控制台,无法把结果存入变量做后续处理(比如判断磁盘是否满了); - 返回值无实际意义:只能通过0/非0判断成败,拿不到具体的错误信息;
- 无法处理复杂命令:带管道符
|、重定向 > 的命令(如 ps -ef | grep python)执行会报错; - 安全性差:存在「命令注入」风险,比如传入的参数包含特殊字符,容易执行恶意命令;
- 无法控制子进程:不能限制命令的执行超时时间、不能捕获子进程的报错信息、不能隐藏命令的控制台输出。
一句话总结 os.system:适合写「一次性、无逻辑、简单执行」的脚本,比如快速写个脚本创建几个文件夹、执行一条简单命令,够用但不适合运维实战。
二、核心重点:subprocess 模块 零基础入门
为什么说 subprocess 是「运维瑞士军刀」?
subprocess 是Python3 官方推荐 的子进程管理模块,Python官方文档明确说明:subprocess 旨在替代 os.system、os.popen、commands 等老旧的命令调用方法。
它的核心优势就像「瑞士军刀」—— 功能全面、灵活可控、安全稳定,既能像 os.system 一样「一行执行简单命令」,也能处理「复杂管道命令、捕获输出结果、控制子进程、设置超时」等所有运维场景的需求,一套模块搞定所有系统命令调用场景。
核心思想:统一的子进程创建逻辑
subprocess 模块的核心是:通过Python创建一个「子进程」去执行系统命令,Python主进程可以完全掌控这个子进程的「输入、输出、错误、执行状态、运行时长」,这也是它比 os.system 强大的核心原因。
而 os.system 本质是「把命令丢给系统Shell执行」,Python完全不参与后续的控制,相当于「放出去的风筝,收不回来」。
必学2个核心方法(够用99%的场景)
subprocess 模块有多个方法,但对初级运维/开发同学来说,不用学全部,只需要掌握 2个核心方法,就能覆盖从「简单命令」到「复杂实战」的所有需求,学习成本极低,性价比拉满!
所有示例均兼容 Python3.6+,Linux/macOS/Windows 通用
方法一:subprocess.run() 「万能基础款」【重中之重】
这是 subprocess 模块的 核心核心核心方法,Python3.5+ 主推的方法,也是最常用的方法,没有之一!所有简单场景都用它,语法简洁、功能齐全,完美替代 os.system。
1. 基础语法(极简用法,对标 os.system)
import subprocess# 语法:subprocess.run("系统命令", shell=True)subprocess.run("ls -l", shell=True)
这个写法和 os.system("ls -l") 效果完全一样,一行执行命令,结果打印到控制台,新手零门槛切换,毫无学习成本!
参数说明:shell=True → 表示「让系统的Shell来执行这条命令」,和 os.system 的执行方式一致,新手必加这个参数,不用纠结,记住就行。
2. 核心进阶:捕获命令的输出结果(解决 os.system 最大痛点)
这是 subprocess 最核心的能力,也是运维脚本最刚需的功能:把命令执行的结果,存入Python变量,做后续逻辑处理。
只需要加两个关键参数,就能完美实现,语法固定,直接套用:
import subprocess# 核心固定写法:捕获命令输出结果res = subprocess.run("df -h", # 要执行的系统命令 shell=True, # 固定加,让Shell执行命令 capture_output=True, # 捕获命令的输出和错误信息 encoding="utf-8"# 编码格式,固定utf-8,避免中文乱码)# 命令的执行结果,存入了 res 变量,后续可以随意使用!print("命令执行结果:\n", res.stdout)
执行上面的代码,你会发现:df -h 的磁盘信息不再打印到控制台,而是被存入了 res.stdout 变量中!
3. subprocess.run() 的返回值(完整可控,告别坑点)
subprocess.run() 执行后,会返回一个 CompletedProcess 对象,这个对象里包含了「命令执行的所有信息」,我们只需要记住4个常用属性,终身受用:
res.returncode :状态码,和 os.system 一致 → 0=成功,非0=失败res.stdoutres.stderr :命令执行的错误信息(字符串),比如命令写错时的报错内容res.args
完整示例,新手直接复制套用:
import subprocess# 执行命令并捕获所有信息cmd = "ps -ef | grep python"res = subprocess.run(cmd, shell=True, capture_output=True, encoding="utf-8")# 1. 判断命令是否执行成功if res.returncode == 0:print("✅命令执行成功!")# 2. 获取命令执行结果,做后续处理 result = res.stdoutprint("命令结果:\n", result)# 比如:统计Python进程数量print(f"Python进程数:{len(result.splitlines())}")else:print("❌命令执行失败!")print("错误信息:", res.stderr)
这个示例,就是运维脚本的标准写法 —— 执行命令 → 判断成败 → 获取结果 → 业务逻辑处理,这是 os.system 完全做不到的!
方法二:subprocess.Popen() 「高阶全能款」
如果说 subprocess.run() 是「基础款瑞士军刀」,那 subprocess.Popen() 就是「顶配款瑞士军刀」!
run() 方法其实是对 Popen() 方法的封装简化,Popen() 是 subprocess 模块的底层核心,提供了极致的灵活性和控制力,能解决所有「复杂场景」的需求,比如:
- 实时获取命令的输出结果(比如执行耗时的脚本,实时打印日志)
核心语法(新手入门版,够用即可)
import subprocess# 基础写法,对标 run() 和 os.systemp = subprocess.Popen("ls -l", shell=True)p.wait() # 等待命令执行完成,等价于 run() 的默认行为
最实用的3个实战写法(运维高频刚需,直接套用)
✨ 实战1:给命令设置「超时时间」(防止脚本卡死,重中之重)
运维脚本最常见的坑:执行的命令卡死(比如 ping 一个不通的地址、远程连接超时),导致整个Python脚本卡死。用 Popen() 可以完美解决,固定写法,必学!
import subprocesscmd = "ping -c 10 www.baidu.com"# 执行10次ping,耗时10秒p = subprocess.Popen(cmd, shell=True)try:# 设置超时时间:5秒,如果5秒内命令没执行完,直接终止 p.wait(timeout=5)print("✅命令在超时时间内执行完成")except subprocess.TimeoutExpired:print("❌命令执行超时!强制终止命令") p.kill() # 终止卡死的子进程
✨ 实战2:静默执行命令,不打印任何内容到控制台
有时候我们执行命令只需要「执行成功即可」,不需要看到输出结果,用这个写法可以让命令「后台静默执行」,控制台干干净净!
import subprocess# devnull 表示「空设备」,所有输出都丢到空设备里,相当于隐藏输出withopen(subprocess.devnull, 'w') as f: subprocess.Popen("mkdir test_dir && touch test.txt", shell=True, stdout=f, stderr=f)print("✅命令静默执行完成")
✨ 实战3:实时获取命令的输出结果(比如执行耗时脚本)
执行备份、解压、批量处理等耗时命令时,需要实时看到命令的执行日志,这个写法可以「一边执行命令,一边打印日志」,体验拉满!
import subprocesscmd = "sh backup.sh"# 你的耗时脚本p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding="utf-8")# 实时读取输出并打印while p.poll() isNone: # poll() 检查命令是否执行完成 line = p.stdout.readline()if line:print(line.strip())print("✅脚本执行完成,状态码:", p.returncode)
三、灵魂拷问:subprocess 到底比 os.system 强在哪?(清晰对比)
看到这里,你应该已经有了直观的感受,这里我们用「大白话+表格」做终极总结,把两者的差异讲透,让你彻底明白为什么说「学了subprocess,就再也不用os.system」。
核心差异本质
- os.system:「委托执行」→ Python把命令丢给系统Shell,自己当「甩手掌柜」,对命令的执行过程无任何控制权;
- subprocess:「亲自掌控」→ Python创建子进程执行命令,全程掌控命令的「输入、输出、错误、超时、生命周期」,想怎么控制就怎么控制。
保姆级对比表(收藏备用)
| | | | |
|---|
| | | os.system 略胜,但subprocess学习成本极低 | |
| | | subprocess 完胜,核心刚需 | |
| | | | |
| | | > >> | |
| | | subprocess 完胜,必用 | |
| | | | |
| | | | |
| | | | |
| | | subprocess 全覆盖,os.system 被替代 | |
✅ 总结
os.system 是「玩具刀」,只能切水果;subprocess 是「瑞士军刀」,能切水果、开啤酒、拧螺丝、剪绳子,功能全面还好用。
四、趣味性+实用性 最佳实践(高频场景,直接套用)
这部分是本文的「精华干货」,全部是运维工作中最常用的实战案例,每个案例都是「可直接复制运行的完整代码」,兼具「趣味性」和「实用性」,新手可以直接拿去用,写脚本效率翻倍!
所有案例均基于 subprocess 模块,兼容Python3.6+,Linux/macOS通用,Windows仅需修改系统命令即可。
实战1:磁盘使用率监控脚本(必写,实用指数⭐⭐⭐⭐⭐)
需求:检查服务器磁盘使用率,如果根目录 / 使用率超过80%,打印告警信息;否则打印正常。
import subprocessdefcheck_disk_usage():# 执行df -h,获取磁盘信息 cmd = "df -h | grep '/dev/vda1' | awk '{print $5}'" res = subprocess.run(cmd, shell=True, capture_output=True, encoding="utf-8")if res.returncode == 0:# 提取使用率,去掉%符号 usage = int(res.stdout.strip().replace("%", ""))if usage >= 80:print(f"⚠️【磁盘告警】根目录使用率{usage}%,超过阈值80%!")else:print(f"✅【磁盘正常】根目录使用率{usage}%")else:print("❌检查磁盘失败:", res.stderr)if __name__ == "__main__": check_disk_usage()
实战2:批量检测服务器端口连通性(高频,实用指数⭐⭐⭐⭐⭐)
需求:批量检测多个服务器的指定端口是否开放,比如检测百度、淘宝、京东的80端口,输出连通状态。
import subprocess# 待检测的服务器列表,格式:(域名/IP, 端口)servers = [("www.baidu.com", 80), ("www.taobao.com", 80), ("www.jd.com", 80), ("192.168.1.1", 22)]defcheck_port(host, port):# 使用nc命令检测端口,超时2秒 cmd = f"nc -z -w 2 {host}{port}" res = subprocess.run(cmd, shell=True, capture_output=True)return res.returncode == 0if __name__ == "__main__":print("=== 端口连通性检测结果 ===")for host, port in servers:if check_port(host, port):print(f"✅{host}:{port}→端口开放")else:print(f"❌{host}:{port}→端口关闭/不通")
实战3:趣味小脚本 - 系统资源监控(趣味性+实用性,指数⭐⭐⭐⭐)
需求:写一个脚本,一键查看服务器的「CPU使用率、内存使用率、磁盘使用率、当前用户数」,输出一个简洁的监控看板,新手也能看懂,成就感拉满!
import subprocessdefget_sys_info():# 1. CPU使用率(top命令,取1次值) cpu_cmd = "top -bn1 | grep 'Cpu(s)' | awk '{print $2 + $4}'" cpu = subprocess.run(cpu_cmd, shell=True, capture_output=True, encoding="utf-8").stdout.strip()# 2. 内存使用率(free命令,计算使用率) mem_cmd = "free -m | grep Mem | awk '{print $3/$2*100}'" mem = subprocess.run(mem_cmd, shell=True, capture_output=True, encoding="utf-8").stdout.strip()# 3. 磁盘使用率(根目录) disk_cmd = "df -h | grep '/dev/vda1' | awk '{print $5}'" disk = subprocess.run(disk_cmd, shell=True, capture_output=True, encoding="utf-8").stdout.strip()# 4. 当前登录用户数 user_cmd = "who | wc -l" user = subprocess.run(user_cmd, shell=True, capture_output=True, encoding="utf-8").stdout.strip()return {"CPU使用率": f"{round(float(cpu), 2)}%","内存使用率": f"{round(float(mem), 2)}%","磁盘使用率": disk,"当前用户数": user }if __name__ == "__main__":print("="*30)print("🎯服务器资源监控看板 (Python+subprocess)")print("="*30) sys_info = get_sys_info()for k, v in sys_info.items():print(f"📌{k}:{v}")print("="*30)
执行这个脚本,你会看到一个漂亮的监控看板,是不是瞬间觉得自己的运维脚本「高大上」了?这就是 subprocess 的魅力!
五、学习总结 & 新手避坑小贴士
学习总结(少走弯路)
- 入门首选:先学
subprocess.run(),这个方法能搞定99%的基础场景,是运维脚本的「主力军」; - 进阶必学:再学
subprocess.Popen() 的3个实战写法(超时、静默、实时输出),解决复杂场景; - 彻底放弃:学会 subprocess 后,就不要再用 os.system 了,除了简单一无是处,还会养成不好的编码习惯;
- 核心原则:所有在Python中执行系统命令的场景,都用subprocess模块,一套方法走天下,不用再记其他模块。
新手避坑小贴士(3个高频坑,记住就好)
- 中文乱码问题:执行命令时,一定要加
encoding="utf-8" 参数,否则中文输出会变成乱码; - shell=True 参数:新手写脚本时,一律加上 shell=True,不用纠结,这样执行命令的方式和 os.system 一致,不会出错;
- 超时时间必加:执行远程命令(如ping、nc、ssh)时,一定要用 Popen 设置超时时间,防止脚本卡死。
结尾:为什么说 subprocess 是的「必备技能」?
作为运维/开发人员,我们写Python脚本的核心目的,是「自动化解决问题」—— 自动化监控、自动化部署、自动化备份、自动化排查故障。
而「执行系统命令」是自动化脚本的核心基石,os.system 只能满足「自动化执行」,却满足不了「自动化判断、自动化处理、自动化告警」。
subprocess 模块的出现,让Python脚本真正具备了「完整的自动化能力」—— 不仅能执行命令,还能捕获结果、判断成败、处理异常、控制执行过程。它就像一把万能的瑞士军刀,看似简单,却能解决运维工作中遇到的所有系统命令调用问题。
对于Python 运维开发人员来说,掌握 subprocess 模块,不是「加分项」,而是「必备项」—— 它能让你写的脚本从「玩具」变成「生产可用的工具」,让你真正体会到Python自动化的魅力。