Contents

正则表达式

参考资料

正则表达式的用途

1、数据验证。例如查看字符串内是否为电话号码模式或信用卡号码模式等等
2、替换文本。识别文档中的特定文本, 完全删除该文本或者用其他文本替换它
3、从字符串中提取子字符串

基础知识

不同的开发环境, 正则表达式可能略有不同

基本匹配

正则表达式 /Cat/g
待匹配文本 The cat sat on the Cat
匹配结果 Cat

元字符

元字符 描述
. 匹配任意字符, 但是不包括换行和回车。等同于 [^\n\r]
[ ] 字符类, 匹配方括号中包含的任意字符
[^ ] 否定字符类。反向筛选
* 匹配前面的子表达式 ≥0 次, 贪婪的 会尽可能多的匹配文字
+ 匹配前面的子表达式 ≥1 次, 贪婪的 会尽可能多的匹配文字
? 匹配前面的子表达式 0/1 次, 或指明一个 非贪婪 限定符
{n,m} 花括号, 匹配前面字符至少 n 次, 但是不超过 m 次, 注意逗号和两个数之间 不能有空格
(xyz) 字符组, 按照确切的顺序匹配字符 xyz
| 分支结构, 匹配符号之前的字符或后面的字符
\ 转义符, 它可以还原元字符原来的含义, 允许你匹配保留字符 [ ] ( ) { } . * + ? ^ $ \ |
^ 匹配行的开始
$ 匹配行的结束

若要匹配特殊字符对应的原本字符, 则需要加 \ 转义

  • 非贪婪/最小匹配

    *+ 的后面加上一个 ?
    例如 <.*?> 表示从 < 开始, 遇到第一个 > 就停止

  • 贪婪匹配

    贪婪匹配会尽可能地返回 满足条件的最长的字符串
    <.*> 表示从 < 开始, 遇到最后一个 > 为止

简写字符集

简写 描述
[\w] 包括字母、数字、下划线。等同于 [A-Za-z0-9_]
[\W] 等同于 [^\w]
\d 匹配一个数字字符。等同于 [0-9]
\D 匹配一个非数字字符。等同于 [^0-9]
[\s] 所有空白符(包括换行)。等同于 [\t\n\f\r\p{Z}]
[\S] 所有非空白符

断言

符号 描述
?= exp1(?=exp2) 查找后面紧跟着 exp2 的 exp1
?! exp1(?!exp2) 查找后面不是 exp2 的 exp1
?<= (?<=exp2)exp1 查找在 exp2 后面的 exp1
?<! (?<!exp2)exp1 查找不在 exp2 后面的 exp1

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 正向先行断言 - 后面必须跟着...
Windows(?=95|98|NT)

# 负向先行断言 - 后面不能跟着...
Windows(?!95|98|NT)

# 正向后行断言 - 前面必须有...
(?<=95|98|NT)Windows

# 负向后行断言 - 前面不能有...
(?<!95|98|NT)Windows

修饰符

标记 描述
i 不区分大小写
g 全局搜索:搜索整个输入字符串中的所有匹配
m 多行匹配

正则表达式 /.at(.)?$/gmi
待匹配文本:

1
2
3
The fat
cat sat
on the mat.

匹配结果:

1
2
3
fat
sat
mat.

分组和后向引用

(\w+) \1(\w+) 是第一个捕获组, \1 表示 匹配与第一个捕获组完全相同的内容
所以 (\w+) \1 匹配的是:“一个单词 + 空格 + 同样的单词”

\w+ \w+:匹配任意两个单词
(\w+) \1:匹配两个相同的单词

  • 查找重复单词

    1
    2
    3
    4
    5
    6
    
    import re
    
    text = "This this is a test test for duplicate duplicate words."
    pattern = re.compile(r'\b(\w+) \1\b', re.IGNORECASE)
    matches = pattern.findall(text)
    print(matches)# ['this', 'test', 'duplicate']
    
  • 匹配对称的 HTML/XML 标签

    1
    2
    3
    4
    5
    
    html = "<h1>Title</h1><div>Content</div><span>Note</span>"
    pattern = re.compile(r'<(\w+)>(.*?)</\1>')
    matches = pattern.findall(html)
    print(matches)
    # [('h1', 'Title'), ('div', 'Content'), ('span', 'Note')]
    
  • 数据清洗, 去除连续重复

    1
    2
    3
    4
    
    text = "This is is a common mistake mistake in writing."
    # 去除连续的重复单词
    cleaned = re.sub(r'\b(\w+) \1\b', r'\1', text)
    print(cleaned)# "This is a common mistake in writing."
    

其他建议和技巧

避免过度使用 .*, 尽量使用更具体的字符类
使用非贪婪匹配 .*? 避免回溯
尽量使用 ^ 和 $ 进行锚定匹配

1
2
3
4
5
6
7
8
# 匹配不以 abc 开头的行
^(?!abc).*$

# 匹配不包含 foo 的行
^(?!.*foo).*$

# 如果前面有 A 则匹配 B, 否则匹配 C
(A)?(?(1)B|C)

实例

1
2
3
4
5
6
7
8
// 邮箱验证
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

# 强密码:至少8位, 包含大小写字母、数字和特殊字符
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$

// 日期格式 YYYY-MM-DD
^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$

匹配手机号

J.Doe:248-555-1234
B.Smith:(313)555-1234
A.Lee:(810)555-1234

上述手机号的形式:区号-局号-路号, 区号和局号的第一位数字不能是 0 或 1
注意括号里面可能会包含空格。例如:(555 )123-1234

[\(.]?[2-9]\d\d[\).]?[-.]?[2-9]\d\d[-.]\d{4}
$$?\d{3}$$?[-.\s]?\d{3}[-.\s]?\d{4}

匹配身份证号码

身份证号码总共 18 位:6+8+4
前 6 位是户口所在地的编码, 其中第一位是 1-8
再 8 位是出生年份, 前两位只能是 18、19、20, 且是可选的, 月份的第一位只能是 0、1, 日期的第一位只能是 0-3
最后 1 位是校验位是数字或者是 X, 是可选的

[1-8]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]

匹配 IP 地址

匹配表达式:(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5))\.){3}

IPv4 地址 \b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b

匹配基础 URL

协议名(http|https), 一个主机名, 一个可选的端口号, 一个文件路径

(https?|ftp|file)://[-\w.]+(:\d+)?(/[\w./?#%&=]*)?

提取纯中文文本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function extractChinese(text) {
  const chineseRegex = /[\p{sc=Han}]+/gu;
  return text.match(chineseRegex) || [];
}

const mixedText = "Hello 你好 World!这是测试123。";
console.log(extractChinese(mixedText));
// 输出: ["你好", "这是测试"]

// 验证是否只包含中文
function isPureChinese(text) {
  return /^[\p{sc=Han}]+$/u.test(text);
}

console.log(isPureChinese("中文测试")); // true
console.log(isPureChinese("中文123")); // false

Python re 模块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import re

text = "Hello 123 World 456"

# findall - 返回所有匹配
matches = re.findall(r'\d+', text)  # ['123', '456']

# search - 返回第一个匹配对象
match = re.search(r'\d+', text)
if match:
    print(match.group())  # 123

# match - 从字符串开头匹配
match = re.match(r'Hello', text)

# split - 根据模式分割字符串
parts = re.split(r'\d+', text)  # ['Hello ', ' World ', '']

# sub - 替换匹配内容
new_text = re.sub(r'\d+', 'NUM', text)  # Hello NUM World NUM
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 预编译正则表达式提高性能
# 编译一次, 多次使用
pattern = re.compile(r'\d{3}-\d{3}-\d{4}')

# 使用命名分组
text = "John Doe: 248-555-1234"
pattern = re.compile(r'(?P<first_name>\w+)\s+(?P<last_name>\w+):\s*(?P<phone>\d{3}-\d{3}-\d{4})')
match = pattern.search(text)
if match:
    print(match.group('first_name'))  # John
    print(match.groupdict())          # {'first_name': 'John', ...}

# 使用后向引用
text = "hello hello world world"
pattern = re.compile(r'(\w+) \1')
matches = pattern.findall(text)  # ['hello', 'world']

修改 GPU 的 Num

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import re

def replace_gpu_numbers(text, target_gpu="0"):
    """
    替换文本中所有的GPU编号

    Args:
        text: 原始文本
        target_gpu: 目标GPU编号

    Returns:
        替换后的文本和替换次数
    """
    pattern = r'cuda:\d'
    replacement = f'cuda:{target_gpu}'

    result, count = re.subn(pattern, replacement, text)
    return result, count

# 使用示例
if __name__ == "__main__":
    original_text = """
    class ConversationBot:
        def __init__(self):
            print("Initializing VisualChatGPT")
            self.llm = OpenAI(temperature=0)
            self.edit = ImageEditing(device="cuda:6")
            self.i2t = ImageCaptioning(device="cuda:4")
            self.t2i = T2I(device="cuda:1")
            self.image2canny = image2canny()
            self.canny2image = canny2image(device="cuda:1")
            self.image2line = image2line()
            self.line2image = line2image(device="cuda:1")
            self.image2hed = image2hed()
            self.hed2image = hed2image(device="cuda:2")
            self.image2scribble = image2scribble()
            self.scribble2image = scribble2image(device="cuda:3")
            self.image2pose = image2pose()
            self.pose2image = pose2image(device="cuda:3")
            self.BLIPVQA = BLIPVQA(device="cuda:4")
            self.image2seg = image2seg()
            self.seg2image = seg2image(device="cuda:7")
            self.image2depth = image2depth()
            self.depth2image = depth2image(device="cuda:7")
            self.image2normal = image2normal()
            self.normal2image = normal2image(device="cuda:5")
            self.pix2pix = Pix2Pix(device="cuda:3")
            self.memory = ConversationBufferMemory(memory_key="chat_history", output_key='output')
    """

    # 找到所有GPU编号
    pattern = re.compile(r'cuda:(\d)')
    gpu_numbers = pattern.findall(original_text)
    print(f"找到的GPU编号: {gpu_numbers}")

    # 统一替换为 cuda:0
    result, count = replace_gpu_numbers(original_text, "0")
    print(f"替换了 {count} 处")
    print(result)