正则表达式零宽断言实战:凌晨3点的服务器报警

凌晨3点,手机的震动在寂静的夜晚显得尤为刺耳。一看,是服务器报警——日志中出现了异常关键词,需要立即排查。你迅速登录服务器,翻阅日志,但日志量庞大,如何快速准确地定位问题?正则表达式零宽断言就是这时的救星。本文将通过这个场景,带你深入了解零宽断言的用法。

场景:凌晨3点的服务器报警

假设你负责的某个服务突然在凌晨3点出现了异常,报警信息中提到日志里出现了“error”关键词。日志文件每天新增几百MB,手动查找显然是个噩梦。这时,你决定使用正则表达式来过滤日志,找出真正的问题所在。

正则表达式的基础

正则表达式(Regular Expression,简称 regex 或 regexp)是一种强大的文本匹配工具,广泛应用于文本搜索和数据提取。大多数编程语言都支持正则表达式,例如 Python、JavaScript 和 Perl 等。正则表达式中的零宽断言是一种特殊的匹配方式,它不消耗字符,只用于验证某些条件是否满足。

零宽断言的核心语法

零宽断言主要有四种类型,分别是:

  • 正向肯定预查 ((?=...))
  • 正向否定预查 ((?!...))
  • 反向肯定预查 ((?<=...))
  • 反向否定预查 ((?<!...))
正向肯定预查 (?=...)

正向肯定预查用于匹配某个位置,该位置后面的部分必须满足给定的正则表达式条件。例如,如果你想匹配所有以“error”开头的行,但不包括“error”本身,可以使用:

import re

log = """
2023-10-01 03:00:01 - INFO - System started
2023-10-01 03:00:02 - ERROR - Something went wrong
2023-10-01 03:00:03 - WARNING - Low memory
2023-10-01 03:00:04 - ERROR - Another issue
"""

# 匹配所有以 "ERROR" 开头的行,但不包括 "ERROR"
pattern = r'(?<=ERROR - ).*'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['Something went wrong', 'Another issue']
正向否定预查 (?!...)

正向否定预查用于匹配某个位置,该位置后面的部分不能满足给定的正则表达式条件。例如,你想匹配所有不以“INFO”开头的行,可以使用:

# 匹配所有不以 "INFO" 开头的行
pattern = r'^(?!INFO).*$'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['2023-10-01 03:00:02 - ERROR - Something went wrong', '2023-10-01 03:00:03 - WARNING - Low memory', '2023-10-01 03:00:04 - ERROR - Another issue']
反向肯定预查 (?<=...)

反向肯定预查用于匹配某个位置,该位置前面的部分必须满足给定的正则表达式条件。例如,你想匹配所有“WARNING”后面的错误信息,可以使用:

# 匹配所有 "WARNING" 后面的错误信息
pattern = r'(?<=WARNING - ).*'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['Low memory']
反向否定预查 (?<!...)

反向否定预查用于匹配某个位置,该位置前面的部分不能满足给定的正则表达式条件。例如,你想匹配所有不以“INFO”开头的行,可以使用:

# 匹配所有不以 "INFO" 开头的行
pattern = r'^(?<!INFO).*$'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['2023-10-01 03:00:02 - ERROR - Something went wrong', '2023-10-01 03:00:03 - WARNING - Low memory', '2023-10-01 03:00:04 - ERROR - Another issue']

实战案例:过滤异常日志

假设日志文件中有一些重复的错误信息,你希望只找出特定的错误信息。例如,你只想找出“error”后面跟有“timeout”的日志行:

# 匹配所有 "ERROR -" 后面跟有 "timeout" 的行
pattern = r'ERROR - (?=.*timeout).*'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['2023-10-01 03:00:02 - ERROR - Something went wrong with a timeout']

高级用法:组合零宽断言

零宽断言可以组合使用,以实现更复杂的匹配需求。例如,你希望找出所有“error”后面跟有“timeout”,并且不以“INFO”开头的日志行:

# 匹配所有 "ERROR -" 后面跟有 "timeout" 且不以 "INFO" 开头的行
pattern = r'^(?!INFO).*(ERROR - (?=.*timeout).*)'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['2023-10-01 03:00:02 - ERROR - Something went wrong with a timeout']

实战案例:排除已知错误

假设你已经知道某些错误信息是误报,例如“error”后面跟有“disk space”是已知的误报,你希望过滤掉这些误报,只保留真正的错误信息:

# 匹配所有 "ERROR -" 后面不跟有 "disk space" 的行
pattern = r'ERROR - (?![^-\n]*disk space).*$'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['2023-10-01 03:00:02 - ERROR - Something went wrong']

实战案例:提取特定错误信息

假设你需要提取“error”后面跟有“500”的日志行,并且只提取错误信息部分(不包括时间戳和日志级别):

# 提取所有 "ERROR -" 后面跟有 "500" 的日志行的错误信息部分
pattern = r'(?<=ERROR - ).*(500).*$'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['Something went wrong with a 500']

实战案例:多条件过滤

假设你需要找出所有“error”后面跟有“500”且不以“INFO”开头的日志行:

# 匹配所有 "ERROR -" 后面跟有 "500" 且不以 "INFO" 开头的行
pattern = r'^(?!INFO).*(ERROR - (?=.*500).*)'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['2023-10-01 03:00:02 - ERROR - Something went wrong with a 500']

实战案例:时间戳过滤

假设你需要找出所有在凌晨3点到4点之间出现的“error”日志行:

# 匹配所有在凌晨 3 点到 4 点之间出现的 "ERROR" 日志行
pattern = r'(\d{4}-\d{2}-\d{2} 03:\d{2}:\d{2} - ERROR - .*)'
matches = re.findall(pattern, log, re.M)

print(matches)
# 输出: ['2023-10-01 03:00:02 - ERROR - Something went wrong', '2023-10-01 03:00:04 - ERROR - Another issue']

总结与工具推荐

通过以上示例,你应该对正则表达式中的零宽断言有了更深入的理解。零宽断言在处理复杂文本匹配问题时非常有用,可以帮助你快速准确地定位和提取所需信息。

如果你对正则表达式还有更多的需求,不妨试试 Hey Cron。Hey Cron 提供了丰富的在线工具,包括正则表达式生成器,帮助你快速生成和测试正则表达式,还可以用于时间戳转换、JSON 格式化、Base64 编码解码等,非常适合开发者在日常工作中使用。

希望这些内容能帮助你在下次遇到类似问题时,迅速解决问题,不再被凌晨3点的报警打扰。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐