4、Python运维基础:Python环境搭建、常用内置模块(os、sys、subprocess、shutil)
说实话,很多运维同学刚开始接触Python时,最头疼的不是语法本身,而是环境搞不定。我见过太多人卡在第一步——装了个Python,结果跑脚本时各种报错,最后怀疑人生。其实没那么复杂,咱们一步步来。
4.1 Python环境搭建
我个人习惯用pyenv来管理多版本Python。为什么?因为生产环境里,你很可能同时维护着Python 2.7的老脚本和Python 3.10的新项目。用系统自带的Python?那简直是给自己挖坑。
核心原则:永远不要动系统自带的Python。用虚拟环境或版本管理工具隔离。
4.1.1 安装Python
Linux下我推荐两种方式:
- 源码编译:适合对版本有严格要求的场景
- 包管理器:适合快速部署
举个例子,在CentOS上:
# 安装依赖
yum install -y gcc openssl-devel bzip2-devel libffi-devel
# 下载Python 3.9.7
wget https://www.python.org/ftp/python/3.9.7/Python-3.9.7.tgz
tar -xf Python-3.9.7.tgz
cd Python-3.9.7
# 编译安装
./configure --prefix=/usr/local/python3.9
make && make install
# 建立软链接
ln -s /usr/local/python3.9/bin/python3.9 /usr/local/bin/python3
ln -s /usr/local/python3.9/bin/pip3.9 /usr/local/bin/pip3
小技巧:编译时加上--enable-optimizations参数,虽然编译时间会长一点,但运行性能能提升10%-15%。我在压测环境里实测过,效果明显。
4.1.2 虚拟环境
虚拟环境这东西,说白了就是给每个项目一个独立的Python“小房间”。你在这个房间里装什么包,都不会影响到外面的世界。
# 创建虚拟环境
python3 -m venv myproject_env
# 激活环境
source myproject_env/bin/activate
# 退出环境
deactivate
我曾经遇到过一个问题:两个项目都依赖requests库,但一个要2.25版本,另一个要2.26版本。如果没有虚拟环境,你只能二选一。有了虚拟环境,各玩各的,互不干扰。
4.2 常用内置模块
Python之所以适合运维,很大程度上是因为它的内置模块太强大了。你想想看,不用装任何第三方包,就能操作文件、执行系统命令、处理路径——这不就是运维的日常吗?
4.2.1 os模块——操作系统的瑞士军刀
os模块是我用得最多的模块之一。它让你能用Python代码直接跟操作系统打交道。
| 函数 | 作用 | 我在项目中的用法 |
|---|---|---|
| os.getcwd() | 获取当前工作目录 | 日志采集脚本中确认运行路径 |
| os.chdir() | 切换目录 | 批量处理不同目录下的配置文件 |
| os.listdir() | 列出目录内容 | 扫描日志目录,找出过期文件 |
| os.mkdir() | 创建目录 | 自动创建备份目录结构 |
| os.remove() | 删除文件 | 清理临时文件 |
| os.path.join() | 拼接路径 | 跨平台路径处理,避免硬编码斜杠 |
看个实际例子:
import os
def clean_old_logs(log_dir, days=7):
"""清理指定目录下超过N天的日志文件"""
import time
now = time.time()
cutoff = now - days * 86400
for filename in os.listdir(log_dir):
filepath = os.path.join(log_dir, filename)
if os.path.isfile(filepath):
mtime = os.path.getmtime(filepath)
if mtime < cutoff:
os.remove(filepath)
print(f"已删除: {filepath}")
# 使用
clean_old_logs("/var/log/myapp", days=3)
注意:用os.remove()删除文件时,不会进回收站。我曾经手滑写错过路径,删掉了不该删的东西。从那以后,我养成了习惯——删除前先打印日志,确认无误再执行。
4.2.2 sys模块——跟Python解释器对话
sys模块让你能获取和修改Python解释器的运行时信息。说白了,就是让你知道“Python自己现在是什么状态”。
最常用的几个:
- sys.argv:获取命令行参数。写脚本工具时必备。
- sys.exit():退出程序并返回状态码。0表示成功,非0表示出错。
- sys.path:模块搜索路径。当你import报错时,先看看这个。
- sys.platform:判断操作系统。跨平台脚本的利器。
import sys
def check_environment():
"""检查运行环境是否满足要求"""
print(f"Python版本: {sys.version}")
print(f"操作系统: {sys.platform}")
print(f"模块搜索路径: {sys.path}")
# 检查Python版本
if sys.version_info.major < 3:
print("错误: 需要Python 3.x")
sys.exit(1)
# 解析命令行参数
if len(sys.argv) > 1:
print(f"传入的参数: {sys.argv[1:]}")
else:
print("没有传入额外参数")
if __name__ == "__main__":
check_environment()
嗯,这里要注意一点:sys.argv[0]是脚本本身的文件名,从sys.argv[1]开始才是真正的参数。我刚开始写脚本时经常搞混这个索引。
4.2.3 subprocess模块——执行系统命令的正确姿势
以前很多运维老哥喜欢用os.system()来执行命令。但说实话,那玩意儿太简陋了。你拿不到输出结果,也控制不了错误处理。后来Python引入了subprocess模块,这才是执行系统命令的正确姿势。
我个人最常用的是subprocess.run():
import subprocess
# 执行命令,获取输出
result = subprocess.run(
["df", "-h"],
capture_output=True,
text=True,
check=False
)
if result.returncode == 0:
print("磁盘使用情况:")
print(result.stdout)
else:
print(f"命令执行失败: {result.stderr}")
几个关键参数说明:
| 参数 | 作用 | 使用场景 |
|---|---|---|
| capture_output=True | 捕获标准输出和错误输出 | 需要分析命令结果时 |
| text=True | 以文本形式返回(而非字节) | 处理人类可读的输出 |
| check=True | 命令失败时抛出异常 | 关键步骤,失败必须中断 |
| shell=True | 通过shell执行(慎用!) | 需要管道、通配符等shell特性 |
安全警告:尽量不要用shell=True,尤其是当命令中包含用户输入时。我曾经见过一个同事因为用了shell=True,导致脚本被注入了恶意命令。如果你必须用,记得先做参数转义。
再看一个实际点的例子——批量检查服务器连通性:
import subprocess
import sys
def ping_host(host, count=3):
"""检查主机是否可达"""
# 根据操作系统选择参数
param = "-n" if sys.platform == "win32" else "-c"
try:
result = subprocess.run(
["ping", param, str(count), host],
capture_output=True,
text=True,
timeout=10,
check=False
)
return result.returncode == 0
except subprocess.TimeoutExpired:
print(f"ping {host} 超时")
return False
# 批量检查
hosts = ["192.168.1.1", "192.168.1.2", "google.com"]
for host in hosts:
status = "可达" if ping_host(host) else "不可达"
print(f"{host}: {status}")
4.2.4 shutil模块——高级文件操作
shutil模块是os模块的升级版。它提供了更高级的文件操作,比如复制整个目录树、移动文件、归档压缩等。
我最常用的几个函数:
- shutil.copy():复制文件
- shutil.copytree():递归复制整个目录
- shutil.move():移动文件或目录
- shutil.rmtree():递归删除目录(比os.remove强大得多)
- shutil.make_archive():创建压缩归档
import shutil
import os
def backup_project(src_dir, backup_dir):
"""备份项目目录"""
# 创建备份目录
os.makedirs(backup_dir, exist_ok=True)
# 生成备份文件名(带时间戳)
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"backup_{timestamp}"
# 创建压缩归档
archive_path = shutil.make_archive(
base_name=os.path.join(backup_dir, backup_name),
format='gztar',
root_dir=src_dir
)
print(f"备份完成: {archive_path}")
return archive_path
# 使用
backup_project("/opt/myapp", "/backup")
避坑指南:我曾经用shutil.rmtree()删除一个临时目录,结果因为路径拼接错误,差点把整个/tmp给删了。从那以后,我每次用rmtree之前,都会先打印一下要删除的路径,确认无误再执行。
4.3 综合实战:日志分析小工具
把今天学的知识串起来,写一个实用的日志分析工具:
import os
import sys
import subprocess
import shutil
from datetime import datetime, timedelta
def analyze_logs(log_dir, days=1):
"""
分析指定目录下的日志文件
1. 找出最近N天的日志
2. 统计错误数量
3. 压缩归档旧日志
"""
if not os.path.exists(log_dir):
print(f"错误: 目录 {log_dir} 不存在")
sys.exit(1)
now = datetime.now()
cutoff = now - timedelta(days=days)
error_count = 0
recent_logs = []
for filename in os.listdir(log_dir):
filepath = os.path.join(log_dir, filename)
if not os.path.isfile(filepath):
continue
# 检查文件修改时间
mtime = datetime.fromtimestamp(os.path.getmtime(filepath))
if mtime > cutoff:
recent_logs.append(filepath)
# 用grep统计错误
result = subprocess.run(
["grep", "-c", "ERROR", filepath],
capture_output=True,
text=True,
check=False
)
if result.returncode == 0 and result.stdout.strip():
error_count += int(result.stdout.strip())
print(f"最近{days}天的日志文件: {len(recent_logs)}个")
print(f"错误总数: {error_count}次")
# 归档7天前的日志
archive_cutoff = now - timedelta(days=7)
old_logs = []
for filename in os.listdir(log_dir):
filepath = os.path.join(log_dir, filename)
if os.path.isfile(filepath):
mtime = datetime.fromtimestamp(os.path.getmtime(filepath))
if mtime < archive_cutoff:
old_logs.append(filepath)
if old_logs:
archive_dir = os.path.join(log_dir, "archive")
os.makedirs(archive_dir, exist_ok=True)
archive_path = shutil.make_archive(
base_name=os.path.join(archive_dir, f"logs_{now.strftime('%Y%m%d')}"),
format='gztar',
root_dir=log_dir,
base_dir='.'
)
# 删除已归档的旧日志
for filepath in old_logs:
os.remove(filepath)
print(f"已归档 {len(old_logs)} 个旧日志文件到: {archive_path}")
return error_count
if __name__ == "__main__":
log_dir = sys.argv[1] if len(sys.argv) > 1 else "/var/log/myapp"
analyze_logs(log_dir)
这个工具虽然简单,但涵盖了今天讲的大部分知识点。你想想看,用不到50行代码,就实现了一个能自动分析、统计、归档的日志工具。这就是Python运维的魅力所在。
最后说一句:这些内置模块只是冰山一角。等你真正用起来,会发现Python的标准库几乎覆盖了运维的方方面面。多写、多用、多踩坑,慢慢就熟练了。