
本文详解为何原始代码中 for 循环未能将全部版本的补丁文件名(如 patch-5.16.2-rt19.patch.gz)追加到列表,核心问题在于正则表达式过于严格且未覆盖真实文件命名变体;通过优化正则模式、增强容错性并添加异常处理,可完整采集 2.6 至 6.9+ 所有有效补丁名。
本文详解为何原始代码中 `for` 循环未能将全部版本的补丁文件名(如 `patch-5.16.2-rt19.patch.gz`)追加到列表,核心问题在于正则表达式过于严格且未覆盖真实文件命名变体;通过优化正则模式、增强容错性并添加异常处理,可完整采集 2.6 至 6.9+ 所有有效补丁名。
在解析 Linux 实时内核(RT Patch)项目页面时,一个常见目标是批量提取各版本对应的补丁包文件名(例如 patch-5.15.37-rt20.patch.xz、patches-6.1.25-rt18.tar.gz 等)。但若正则表达式设计不当,即使 for 循环逻辑正确、HTTP 请求成功、遍历无遗漏,最终结果仍会严重缺失——正如原始代码所示:看似“只返回了早期版本(如 2.6)”,实则是匹配失败导致静默跳过后续所有版本。
根本原因在于原始正则 r'patch-.*?\.tar\.xz' 过度限定:
- ✅ 仅匹配以
patch-开头、以.tar.xz结尾的字符串; - ❌ 完全忽略真实存在的多种格式:
.patch.gz、.patch.xz、patches-*.tar.gz、patches-*.tar.xz等; - ❌ 且
.*?在遇到首个.即停止,无法兼容-rtXX后缀中的连字符与数字组合。
因此,当程序访问 https://cdn.kernel.org/pub/linux/kernel/projects/rt/5.16/sha256sums.asc 时,4 个真实补丁名全部不满足该模式,re.findall() 返回空列表,patches_full_versions.append() 实际从未执行——并非数组被重置或循环中断,而是每轮迭代都“成功地什么也没加”。
以下是修复后的健壮实现:
import re
import requests
patches_versions = []
patches_full_versions = []
RT_PATCHES_BASE_URL = "https://cdn.kernel.org/pub/linux/kernel/projects/rt/"
# 清理 HTML 标签(用于解析索引页)
cleaner = re.compile(r"<.*?>")
# 步骤1:提取所有可用版本目录名(如 "5.16", "6.1")
try:
patches_page_content = requests.get(RT_PATCHES_BASE_URL, timeout=10)
patches_page_content.raise_for_status()
stripped_content = re.sub(cleaner, "", patches_page_content.text)
for line in stripped_content.splitlines():
# 使用更可靠的判断:行中含数字即视为潜在版本行
if re.search(r"\d", line):
parts = line.strip().split("/")
if parts and parts[0].strip(): # 避免空字符串
patches_versions.append(parts[0].strip())
except Exception as e:
print(f"[ERROR] Failed to fetch versions list: {e}")
exit(1)
# 步骤2:为每个版本抓取 sha256sums.asc 并提取补丁文件名
patch_name_pattern = re.compile(r"(?:patch|patches)[\w\-\.]+(?:\.patch|\.tar)\.(?:gz|xz)") # 支持主流扩展名
for version in patches_versions:
url = f"{RT_PATCHES_BASE_URL}{version}/sha256sums.asc"
try:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
# 提取所有匹配的补丁名(去重可选)
matches = re.findall(patch_name_pattern, resp.text)
patches_full_versions.extend(matches) # 推荐用 extend 替代多次 append
print(f"[INFO] Version {version}: found {len(matches)} patches")
except requests.exceptions.RequestException as e:
print(f"[WARN] Skipping {url} — request failed: {e}")
except Exception as e:
print(f"[WARN] Unexpected error parsing {url}: {e}")
print(f"\n✅ Total collected patches: {len(patches_full_versions)}")
print("First 5 examples:", patches_full_versions[:5])关键改进说明:
-
正则升级:
r"(?:patch|patches)[\w\-\.]+(?:\.patch|\.tar)\.(?:gz|xz)"-
(?:patch|patches)非捕获分组,覆盖两种前缀; -
[\w\-\.]+宽松匹配版本号、-rtX、点号和连字符; -
(?:\.patch|\.tar)明确支持.patch和.tar两类主后缀; -
\.(?:gz|xz)精确限定压缩格式,避免误匹配其他扩展名(如.asc,.sig)。
-
-
健壮性增强:
- 全局
try/except包裹 HTTP 请求,防止单个失败中断整个流程; -
timeout=10避免请求挂起; - 使用
extend()批量添加,比循环append()更高效; - 添加日志输出,便于定位哪一版缺失或失败。
- 全局
-
注意事项:
-
sha256sums.asc文件内容为纯文本哈希列表(如a1b2c3... patch-6.6.12-rt7.patch.xz),无需 HTML 解析,原代码中对它的正则直接作用于resp.text是正确的; - 若需进一步去重或按版本分组,可在收集后用
collections.defaultdict(list)处理; - 生产环境建议添加 User-Agent 头(
headers={"User-Agent": "Mozilla/5.0..."}),避免部分 CDN 拒绝爬虫请求。
-
运行修复后代码,你将看到 patches_full_versions 真实包含从 2.6 到最新 6.x 的全部补丁文件名,验证方式:检查输出总数是否与各 sha256sums.asc 页面手动统计一致,并确认示例中包含 patch-5.16.2-rt19.patch.gz 等此前被遗漏的格式。


















