4、脚本基础语法(二):条件判断、循环控制与异常处理
好,咱们接着聊脚本语法。上一章我们把变量、数据类型和运算符过了一遍,那些是脚本的「砖块」。这一章要讲的,是让砖块动起来的「水泥」——条件判断、循环控制和异常处理。
说白了,没有这些控制结构,你的脚本就只能从上往下一条条执行,跟流水账似的。遇到不同情况做不同处理?重复执行某个操作?程序出错了怎么办?这些都得靠今天的内容来解决。
- 条件判断:让脚本会「思考」,根据条件走不同分支
- 循环控制:让脚本会「重复」,批量处理数据
- 异常处理:让脚本会「兜底」,出错了也不崩
4.1 条件判断:让脚本学会「看情况办事」
条件判断是脚本里最常用的控制结构。没有它,你的脚本就是个「一根筋」——不管输入什么,都走同一条路。
4.1.1 if / elif / else 基础结构
Python 的条件判断写法很直观。我刚开始写脚本时,最常犯的错误就是忘了冒号,或者缩进不对。记住:冒号开头,缩进控制代码块。
# 基础 if 结构
status = "running"
if status == "running":
print("服务运行正常")
elif status == "stopped":
print("服务已停止,需要启动")
elif status == "error":
print("服务异常,请检查日志")
else:
print("未知状态,请人工确认")
4.1.2 条件表达式与逻辑运算符
实际项目中,条件往往不是单一的。比如检查服务器状态,既要看进程是否存活,又要看端口是否监听。这时候就需要组合条件。
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
and |
与 | a > 0 and b < 10 |
两个条件都成立才为真 |
or |
或 | a == 0 or b == 0 |
至少一个条件成立即为真 |
not |
非 | not a > 0 |
取反操作 |
in |
成员判断 | "error" in log |
检查是否包含某个元素 |
is |
身份判断 | a is None |
检查是否为同一个对象 |
# 组合条件实战
cpu_usage = 85
memory_usage = 92
disk_usage = 78
# 检查是否需要告警
if cpu_usage > 90 and memory_usage > 85:
print("⚠️ 严重告警:CPU和内存同时高负载")
elif cpu_usage > 80 or memory_usage > 80:
print("⚠️ 警告:CPU或内存负载偏高")
elif not disk_usage > 90:
print("✅ 磁盘空间充足,无需处理")
else:
print("✅ 系统运行正常")
== 判断浮点数相等。比如 0.1 + 0.2 == 0.3 返回的是 False!因为浮点数精度问题。后来我改用 abs(a - b) < 0.0001 这种方式来判断,再也没出过问题。
4.2 循环控制:让脚本学会「重复劳动」
运维脚本里,循环用得特别多。批量检查服务器、遍历日志文件、重试失败的操作……这些场景都离不开循环。
4.2.1 for 循环:遍历利器
for 循环适合遍历已知范围的数据。我个人觉得,能用 for 解决的问题,尽量别用 while——代码更清晰,也不容易写出死循环。
# 遍历列表
servers = ["web-01", "web-02", "db-01", "cache-01"]
for server in servers:
print(f"正在检查 {server} 的状态...")
# 这里可以调用检查函数
# 遍历字典
config = {"host": "192.168.1.1", "port": 8080, "timeout": 30}
for key, value in config.items():
print(f"{key} = {value}")
# 使用 range() 生成序列
for i in range(1, 6):
print(f"第 {i} 次重试...")
4.2.2 while 循环:条件驱动
while 循环适合你不知道要循环多少次,但知道什么时候该停的场景。比如等待某个服务启动完成。
False,否则就是死循环。我见过太多线上事故是因为 while 条件写错了,脚本跑了一整夜没停。
# 等待服务启动
import time
service_ready = False
retry_count = 0
max_retries = 10
while not service_ready and retry_count < max_retries:
print(f"检查服务状态... 第 {retry_count + 1} 次")
# 模拟检查服务
if retry_count >= 3: # 假设第4次检查成功
service_ready = True
print("服务已就绪!")
else:
print("服务尚未就绪,等待 2 秒...")
time.sleep(2)
retry_count += 1
if not service_ready:
print("服务启动超时,请手动检查")
4.2.3 循环控制:break、continue 与 else
这三个关键字是循环的「方向盘」。用好了,代码会非常优雅;用不好,逻辑会乱成一团。
| 关键字 | 作用 | 使用场景 |
|---|---|---|
break |
立即跳出整个循环 | 找到目标后提前结束 |
continue |
跳过本次循环,进入下一次 | 过滤掉不需要处理的数据 |
else |
循环正常结束(未被 break)时执行 | 检查是否找到了目标 |
# break 示例:找到第一个错误日志就停止
logs = ["info: 正常", "info: 正常", "error: 连接超时", "info: 正常", "error: 权限拒绝"]
for log in logs:
if "error" in log:
print(f"发现错误:{log}")
break # 找到第一个错误就停止
else:
print("未发现任何错误") # 只有循环正常结束才会执行
# continue 示例:只处理 error 级别的日志
for log in logs:
if "info" in log:
continue # 跳过 info 日志
print(f"处理错误日志:{log}")
else 子句很多人不知道。它特别适合「查找-确认」场景——如果循环里用 break 找到了目标,else 就不执行;如果找了一圈都没找到,else 就会触发。比设个标志变量优雅多了。
4.3 异常处理:让脚本学会「扛得住」
写脚本时,最怕的就是「未捕获的异常」。脚本跑到一半突然崩了,前面的工作全白费。异常处理就是给你的脚本穿上「防弹衣」。
4.3.1 try / except 基础
你想想看,运维脚本要处理各种不确定因素:网络超时、文件不存在、权限不足……这些都得靠异常处理来兜底。
# 基础异常处理
try:
# 可能会出错的代码
with open("/etc/config.ini", "r") as f:
content = f.read()
print("配置文件读取成功")
except FileNotFoundError:
print("配置文件不存在,使用默认配置")
except PermissionError:
print("权限不足,请检查文件权限")
except Exception as e:
print(f"未知错误:{e}")
4.3.2 捕获多个异常与异常链
实际项目中,一个操作可能引发多种异常。我习惯把能预见的异常单独处理,最后留一个兜底的 Exception。
# 多异常捕获
import requests
try:
response = requests.get("http://example.com/api/status", timeout=5)
data = response.json()
print(f"API 返回:{data}")
except requests.ConnectionError:
print("网络连接失败,请检查网络")
except requests.Timeout:
print("请求超时,服务可能不可用")
except ValueError as e:
print(f"JSON 解析失败:{e}")
except Exception as e:
print(f"未预期的错误:{e}")
# 记录日志,方便排查
except 子句。早期写脚本时,为了省事,直接写 except: pass,结果线上出了问题,脚本静默吞掉了所有异常,连日志都没留下。排查了整整一天才发现是某个接口改了返回格式。现在我的原则是:要么明确捕获,要么记录日志,绝不静默吞异常。
4.3.3 finally:无论如何都要执行的代码
finally 子句是异常处理里的「保险」。不管有没有异常,不管有没有 return,finally 里的代码一定会执行。资源清理、关闭连接、释放锁……这些操作放 finally 里最合适。
# finally 实战:确保文件关闭
def read_config(file_path):
f = None
try:
f = open(file_path, "r")
return f.read()
except FileNotFoundError:
print("文件不存在")
return None
finally:
if f:
f.close() # 确保文件被关闭
print("文件已关闭")
# 即使 try 里有 return,finally 也会执行
result = read_config("/etc/config.ini")
print(f"读取结果:{result}")
4.3.4 自定义异常与 raise
有时候,内置的异常类型不够用。比如你想定义「磁盘空间不足」这种业务异常。这时候可以自定义异常类。
# 自定义异常
class DiskFullError(Exception):
"""磁盘空间不足异常"""
def __init__(self, disk_path, usage_percent):
self.disk_path = disk_path
self.usage_percent = usage_percent
super().__init__(f"磁盘 {disk_path} 使用率已达 {usage_percent}%")
# 使用自定义异常
def check_disk_space(path):
# 模拟检查磁盘
usage = 95
if usage > 90:
raise DiskFullError(path, usage)
return True
try:
check_disk_space("/data")
except DiskFullError as e:
print(f"⚠️ {e}")
# 触发告警或自动清理逻辑
- 精确捕获:能捕获具体异常就别用
Exception - 保留异常链:使用
raise ... from ...保留原始异常信息 - 不要滥用:能用条件判断解决的问题,别用异常处理
- 记录日志:捕获异常后一定要记录,方便事后排查
4.4 综合实战:一个完整的脚本片段
把今天学的三个知识点串起来,写一个实际能用的脚本。这个脚本会批量检查服务器状态,遇到错误会重试,重试失败会记录异常。
import time
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def check_server(server_ip, port=80):
"""检查服务器是否存活"""
# 模拟检查逻辑
import random
return random.choice([True, False])
def batch_check_servers(server_list, max_retries=3):
"""批量检查服务器,支持重试"""
results = {}
for server in server_list:
logging.info(f"开始检查服务器:{server}")
success = False
for attempt in range(1, max_retries + 1):
try:
logging.info(f"第 {attempt} 次尝试检查 {server}")
status = check_server(server)
if status:
results[server] = "正常"
success = True
logging.info(f"{server} 检查通过")
break # 检查通过,跳出重试循环
else:
logging.warning(f"{server} 返回异常状态")
except ConnectionError as e:
logging.error(f"{server} 连接失败:{e}")
if attempt < max_retries:
wait_time = attempt * 2 # 递增等待时间
logging.info(f"等待 {wait_time} 秒后重试...")
time.sleep(wait_time)
else:
results[server] = f"连接失败(已重试{max_retries}次)"
except Exception as e:
logging.error(f"{server} 发生未知错误:{e}")
results[server] = f"未知错误:{str(e)}"
break # 未知错误不重试
else:
# 循环正常结束(未被 break),说明所有重试都失败了
if server not in results:
results[server] = f"检查失败(已重试{max_retries}次)"
return results
# 执行批量检查
servers_to_check = ["192.168.1.1", "192.168.1.2", "192.168.1.3"]
check_results = batch_check_servers(servers_to_check)
# 输出结果
print("\n=== 检查结果汇总 ===")
for server, status in check_results.items():
if "正常" in status:
print(f"✅ {server}: {status}")
else:
print(f"❌ {server}: {status}")
for 循环遍历服务器列表,if/else 判断检查结果,try/except 处理连接异常,break 在成功时跳出循环,else 子句处理所有重试都失败的情况。你看,把这些基础结构组合起来,就能写出很实用的运维脚本。
嗯,这一章的内容就到这里。条件判断、循环控制、异常处理,这三个东西是脚本逻辑的「三驾马车」。写脚本时多想想:什么情况走哪条路?哪些操作需要重复?出错了怎么兜底?想清楚了,脚本的质量自然就上去了。