跳转至

18. 正则表达式(Regular Expression / Regex)

一句话说明:正则表达式是一套"文本搜索模式语言",让你用一行代码就能从海量文本中精准找到、提取、替换你想要的内容——生信分析中处理序列文件、注释文件、日志文件的必备技能。


一、核心概念(白话版)

1.1 正则是什么?

正则表达式(Regular Expression,简称 regex 或 regexp)就是一个描述文本模式的"模板"

白话比方:想象你在一本电话簿里找电话号码。你不知道具体号码是多少,但你知道"电话号码长这样:3位区号-8位数字"。正则表达式就是帮你把这个"长这样"的规则写成代码能懂的语言。

模板:\d{3}-\d{8}
意思:3个数字 + 一个横杠 + 8个数字
能匹配:010-12345678、021-87654321
不能匹配:abc-12345678、01-1234

1.2 为什么生信要学正则?

场景 不用正则 用正则
从 FASTA 文件提取基因名 一行行肉眼看,或写很多 if 判断 一行代码搞定
批量改文件名 手动一个个改 sed 一行搞定
从 GFF 文件提取 gene_id 写很多 split + 循环 grep -oP 一行搞定
过滤质量低的序列ID 写复杂的字符串判断 正则匹配模式一步到位

总结:正则 = 文本处理的瑞士军刀,生信离不开它。

1.3 白话总结

  • 正则 = 一套描述"文本长什么样"的规则语言
  • 匹配 = 看某段文字符不符合这个规则
  • 捕获 = 从符合规则的文字中,抠出你要的那部分
  • 替换 = 把符合规则的文字换成别的

二、基础语法(每个都有白话解释 + 生信实例)

2.1 字符匹配

语法 白话解释 生信实例 匹配结果
. 匹配任意一个字符(除了换行符)。就像扑克牌里的"万能牌" A.G 匹配 DNA 中的 ATGACGAAG ATG ✓ ACG ✓ AG ✗
\d 匹配一个数字(0-9)。d = digit(数字) sample_\d 匹配 sample_1sample_9 sample_1 ✓ sample_a ✗
\w 匹配一个"单词字符"(字母、数字、下划线)。w = word gene_\w+ 匹配 gene_abcgene_123 gene_abc ✓ gene-abc ✗
\s 匹配一个空白字符(空格、Tab、换行)。s = space ID\s+name 匹配 ID name(多个空格) ID name ✓ IDname ✗
[abc] 匹配方括号里任意一个字符。就像"从 a、b、c 里选一个" [ATCG] 匹配 DNA 碱基 A ✓ T ✓ X ✗
[^abc] 匹配不在方括号里的任意字符^[] 里表示"取反" [^ATCG] 匹配非标准碱基 N ✓ A ✗

补充常用字符类

[A-Z]     匹配任意一个大写字母
[a-z]     匹配任意一个小写字母
[0-9]     等同于 \d,匹配一个数字
[A-Za-z]  匹配任意一个英文字母
[ATCG]    匹配 DNA 的四种碱基(生信专用)
[ACGU]    匹配 RNA 的四种碱基

2.2 量词(控制"出现几次")

语法 白话解释 生信实例 匹配结果
* 前面的东西出现 0 次或多次。"有没有都行,有就尽量多" AT*G → A后面跟0或多个T再跟G AG ✓ ATG ✓ ATTTG ✓
+ 前面的东西出现 1 次或多次。"至少要有一个" AT+G → A后面至少1个T再跟G ATG ✓ ATTTG ✓ AG ✗
? 前面的东西出现 0 次或 1 次。"可有可无" colou?r → u 可有可无 color ✓ colour ✓
{n} 前面的东西恰好出现 n 次 [ATCG]{3} → 恰好3个碱基(一个密码子) ATG ✓ AT ✗ ATCG ✗
{n,m} 前面的东西出现 n 到 m 次 [ATCG]{20,30} → 20-30个碱基的序列 (20-30 bp 的短序列)

白话记忆口诀: - * → "星星"可以没有(0次起步) - + → "加号"至少加一个(1次起步) - ? → "问号"就是在问"要不要"(0或1次)

2.3 定位(锚点——不匹配字符,匹配"位置")

语法 白话解释 生信实例 说明
^ 匹配行的开头。"我要找的东西必须在最前面" ^> 匹配 FASTA 文件中以 > 开头的行(头信息行) 只匹配行首的 >
$ 匹配行的结尾。"我要找的东西必须在最后面" \*$ 匹配蛋白质序列末尾的终止符 * 只匹配行末的 *
\b 匹配单词边界。"一个单词的开头或结尾位置" \bgene\b 只匹配独立的 gene,不匹配 genesgenetically 精确匹配整个单词

注意区分: - ^[] 外面 = 行开头 - ^[] 里面 = 取反(如 [^ATCG]

2.4 分组与或

语法 白话解释 生信实例
() 分组:把多个字符当成一个整体,同时"记住"匹配到的内容(捕获) (gene_id\|transcript_id) "([^"]+)" → 捕获 GFF 中的 ID 值
\| :匹配左边或右边。就像"A 或者 B" ATG\|GTG\|TTG → 匹配三种起始密码子

注意:表格中的 \| 是 Markdown 表格语法需要的转义。在实际 Python/Shell 正则中直接写 | 即可,如 ATG|GTG|TTG

分组的两大用途: 1. 整体操作(AT){3} 匹配 ATATAT(AT 重复3次) 2. 捕获提取:用 () 圈住你要的部分,之后可以单独取出来

2.5 转义

语法 白话解释 实例
\ 让特殊字符变成普通字符。就像给特殊字符"取消超能力" \. 匹配真正的句号(不是"任意字符")

什么时候需要转义? 当你想匹配这些字符本身时:. * + ? ^ $ { } [ ] ( ) | \

想匹配文件名 "result.txt":
  错误:result.txt   → 这里的 . 会匹配任意字符,比如 "resultXtxt"
  正确:result\.txt   → \. 就是匹配真正的句号

三、生信常用正则模式(每个都有完整实例)

3.1 匹配 FASTA 头信息

FASTA 文件格式:每条序列以 > 开头的描述行,后面跟序列。

示例数据:
>gi|12345|ref|NM_001234.5| Homo sapiens gene ABC
ATGCGATCGATCG
>sp|P12345|BRCA1_HUMAN BRCA1 protein
MSDKEVTDPS
# 匹配 FASTA 头信息行
pattern = r'^>(.+)$'
# 解释:^ 行开头,> 就是大于号,(.+) 捕获后面所有内容,$ 行结尾

# 提取 NCBI gi 号
pattern = r'>gi\|(\d+)\|'
# 解释:\| 是转义的竖线(因为 | 在正则里是"或"),(\d+) 捕获数字

# 提取 UniProt ID(如 P12345)
pattern = r'>sp\|([A-Z0-9]+)\|'

3.2 提取基因 ID

# 从 GFF/GTF 属性字段提取 gene_id
# 示例行:gene_id "ENSG00000139618"; transcript_id "ENST00000357654";
pattern = r'gene_id "([^"]+)"'
# 解释:[^"]+ 匹配引号内的所有内容(除了引号本身)

# 提取 Ensembl 基因 ID(以 ENSG 开头,后跟11位数字)
pattern = r'(ENSG\d{11})'
# 解释:ENSG 后面跟恰好11个数字

# 提取 NCBI Gene ID(纯数字)
pattern = r'GeneID:(\d+)'

3.3 匹配 DNA / 蛋白质序列

# 纯 DNA 序列(只含 ATCGN)
pattern = r'^[ATCGNatcgn]+$'
# 解释:从头到尾都是碱基字符

# 匹配起始密码子
pattern = r'ATG'

# 匹配终止密码子
pattern = r'(TAA|TAG|TGA)'

# 匹配一个完整的开放阅读框(ORF)—— 简化版
pattern = r'ATG([ATCG]{3})*(TAA|TAG|TGA)'
# 解释:ATG 开始,中间是3的倍数个碱基,以终止密码子结束

# 蛋白质序列(20种氨基酸单字母)
pattern = r'^[ACDEFGHIKLMNPQRSTVWY]+$'

# 带终止符的蛋白质
pattern = r'^[ACDEFGHIKLMNPQRSTVWY]+\*?$'

3.4 解析 GFF/GTF 文件

GFF3 格式每行9列,Tab 分隔:

chr1  ensembl  gene  11869  14409  .  +  .  ID=ENSG00000223972;Name=DDX11L1
chr1  ensembl  exon  11869  12227  .  +  .  Parent=ENST00000456328
# 提取基因名
pattern = r'Name=([^;]+)'
# 解释:Name= 后面到分号之前的内容

# 提取坐标信息(第4、5列)
pattern = r'^\S+\t\S+\t(\S+)\t(\d+)\t(\d+)'
# 解释:\S+ 匹配非空白字符,\t 匹配 Tab

# 只提取 gene 类型的行
pattern = r'^\S+\t\S+\tgene\t'

# 从 GTF 提取 gene_id 和 gene_name
pattern = r'gene_id "([^"]+)".*gene_name "([^"]+)"'

3.5 从文件名提取样本编号

# 文件名格式:sample_01_R1.fastq.gz
pattern = r'sample_(\d+)_R([12])\.fastq\.gz'
# 解释:(\d+) 捕获样本号,([12]) 捕获 R1 或 R2

# 更复杂的文件名:T2D_patient03_gut_16S_R1.fq.gz
pattern = r'(\w+?)_patient(\d+)_(\w+)_(\w+)_R([12])\.fq\.gz'
# 捕获组:1=项目名,2=患者号,3=样本类型,4=测序方法,5=端号

# 批量提取样本 ID(只要数字部分)
pattern = r'[A-Za-z_]*(\d+)'

四、Python re 模块实操

4.1 四大核心函数

import re

# ===== 准备示例数据 =====
fasta_header = ">gi|12345|ref|NM_001234.5| Homo sapiens BRCA1"
dna_sequence = "ATGCGATCGATCGATCGTAA"
gtf_line = 'chr1\tensembl\tgene\t11869\t14409\t.\t+\t.\tgene_id "ENSG00000223972"; gene_name "DDX11L1";'


# ==========================================
# 1. re.search() —— 在字符串中搜索第一个匹配
# ==========================================
# 白话:从整个字符串中找到第一个符合条件的地方
result = re.search(r'gi\|(\d+)\|', fasta_header)
if result:
    print(f"找到 GI 号: {result.group(1)}")   # 输出: 找到 GI 号: 12345
    print(f"完整匹配: {result.group(0)}")      # 输出: 完整匹配: gi|12345|
    print(f"匹配位置: {result.start()}-{result.end()}")  # 输出位置


# ==========================================
# 2. re.match() —— 从字符串开头匹配
# ==========================================
# 白话:只在字符串的最开头找,不从中间找
# 注意:match 只匹配开头!这是它和 search 最大的区别

# 这个能匹配,因为 > 在开头(match 自动从开头匹配,^ 可省略但加上更清晰)
result = re.match(r'>(.+)', fasta_header)
if result:
    print(f"头信息: {result.group(1)}")

# 这个不能匹配,因为 gi 不在开头(前面有 >)
result = re.match(r'gi', fasta_header)
print(result)   # 输出: None


# ==========================================
# 3. re.findall() —— 找到所有匹配,返回列表
# ==========================================
# 白话:把所有符合条件的全部找出来,打包成一个列表

# 找 DNA 中所有的密码子(每3个碱基一组)
codons = re.findall(r'[ATCG]{3}', dna_sequence)
print(f"所有密码子: {codons}")
# 输出: ['ATG', 'CGA', 'TCG', 'ATC', 'GAT', 'CGT']
# 注意:最后剩 "AA" 不足3个字符,{3} 匹配不到,所以被丢弃
# findall 是从左到右非重叠匹配,每次吃掉3个字符再继续

# 从 GTF 行中提取所有引号内的值
values = re.findall(r'"([^"]+)"', gtf_line)
print(f"所有属性值: {values}")
# 输出: ['ENSG00000223972', 'DDX11L1']

# 多个捕获组时,返回元组列表
pairs = re.findall(r'(\w+_\w+) "([^"]+)"', gtf_line)
print(f"键值对: {pairs}")
# 输出: [('gene_id', 'ENSG00000223972'), ('gene_name', 'DDX11L1')]


# ==========================================
# 4. re.sub() —— 替换匹配到的内容
# ==========================================
# 白话:找到符合条件的文字,换成你想要的

# 把 DNA 序列中的 T 换成 U(DNA → RNA 转录)
rna = re.sub(r'T', 'U', dna_sequence)
print(f"RNA序列: {rna}")
# 输出: AUGCGAUCGAUCGAUCGUAA

# 清理 FASTA 头信息,只保留基因名
clean = re.sub(r'>gi\|\d+\|ref\|[\w.]+\|\s*', '', fasta_header)
print(f"基因名: {clean}")
# 输出: Homo sapiens BRCA1

# 标准化样本名(去掉多余的下划线和空格)
messy_name = "sample__01___R1"
clean_name = re.sub(r'_+', '_', messy_name)     # 多个下划线变一个
print(f"清理后: {clean_name}")
# 输出: sample_01_R1

4.2 分组捕获详解

import re

# ===== 命名分组(推荐!代码更易读)=====
# 语法:(?P<名字>pattern)
gtf_line = 'gene_id "ENSG00000223972"; gene_name "DDX11L1"; transcript_id "ENST00000456328";'

# 使用命名分组提取
pattern = r'gene_id "(?P<gene_id>[^"]+)".*gene_name "(?P<gene_name>[^"]+)"'
match = re.search(pattern, gtf_line)

if match:
    print(f"基因ID: {match.group('gene_id')}")       # ENSG00000223972
    print(f"基因名: {match.group('gene_name')}")      # DDX11L1
    print(f"所有捕获: {match.groupdict()}")            # 返回字典
    # {'gene_id': 'ENSG00000223972', 'gene_name': 'DDX11L1'}


# ===== 实战:批量解析 FASTA 文件 =====
fasta_content = """>sp|P12345|BRCA1_HUMAN BRCA1 protein
MSDKEVTDPS
>sp|Q67890|TP53_HUMAN Tumor protein p53
MEEPQSDPSVEPPLSQ"""

# 提取所有蛋白质的 UniProt ID 和蛋白名
pattern = r'>sp\|([A-Z0-9]+)\|(\w+)\s+(.+)'
results = re.findall(pattern, fasta_content)

for uniprot_id, protein_code, description in results:
    print(f"UniProt: {uniprot_id}, 代码: {protein_code}, 描述: {description}")
# 输出:
# UniProt: P12345, 代码: BRCA1_HUMAN, 描述: BRCA1 protein
# UniProt: Q67890, 代码: TP53_HUMAN, 描述: Tumor protein p53


# ===== 实战:用 re.sub 和分组进行格式转换 =====
# 把 "姓 名" 格式转成 "名, 姓" 格式
authors = "Zhang Wei, Li Ming, Wang Fang"
# 每个名字是:一个大写字母开头的单词 + 空格 + 一个大写字母开头的单词
converted = re.sub(r'(\b[A-Z]\w+)\s+(\b[A-Z]\w+)', r'\2, \1', authors)
# \1 = 第一个分组(姓),\2 = 第二个分组(名)
print(f"转换后: {converted}")


# ===== re.compile() —— 预编译提高效率 =====
# 如果同一个正则要用很多次,先编译再用,速度更快
gene_pattern = re.compile(r'gene_id "([^"]+)"')

# 模拟逐行处理 GTF 文件
gtf_lines = [
    'chr1\t.\tgene\t100\t200\t.\t+\t.\tgene_id "ENSG00000001"; gene_name "GENE_A";',
    'chr1\t.\texon\t100\t150\t.\t+\t.\tgene_id "ENSG00000001"; exon_number "1";',
    'chr2\t.\tgene\t300\t500\t.\t-\t.\tgene_id "ENSG00000002"; gene_name "GENE_B";',
]

gene_ids = set()
for line in gtf_lines:
    match = gene_pattern.search(line)
    if match:
        gene_ids.add(match.group(1))

print(f"所有基因ID: {gene_ids}")
# 输出: {'ENSG00000001', 'ENSG00000002'}

五、Shell 中的正则(grep / sed / awk)

5.1 grep —— 搜索匹配的行

# -E  使用扩展正则(推荐,不用转义 +、? 等)
# -P  使用 Perl 正则(功能最强,支持 \d 等)
# -o  只输出匹配到的部分(不是整行)
# -i  忽略大小写
# -c  统计匹配的行数
# -n  显示行号
# -v  反转匹配(显示不匹配的行)

# 1. 从 FASTA 文件中提取所有头信息行
grep '^>' sequences.fasta

# 2. 统计 FASTA 中有多少条序列
grep -c '^>' sequences.fasta

# 3. 提取所有基因 ID(Ensembl 格式)
grep -oP 'ENSG\d{11}' annotation.gtf

# 4. 找到包含特定基因名的行
grep -i 'brca1' annotation.gff

# 5. 过滤掉注释行(以 # 开头的行)
grep -v '^#' data.gff > clean_data.gff

# 6. 找 DNA 序列中的起始密码子位置
grep -n 'ATG' sequence.txt

# 7. 同时搜索多个模式
grep -E 'gene|exon|CDS' annotation.gff

# 8. 提取 fastq 文件中的序列 ID
# 注意:@ 不是正则特殊字符,不需要转义
grep -P '^@\S+' reads.fastq | head

5.2 sed —— 流编辑器(搜索替换)

# 基本语法:sed 's/查找/替换/标志' 文件

# 1. 把 FASTA 头信息中的空格替换成下划线
sed 's/ /_/g' sequences.fasta
# g = global,替换每行中所有匹配(不加 g 只替换第一个)

# 2. 删除空行
sed '/^$/d' file.txt
# d = delete,删除匹配到的行

# 3. 提取 FASTA 序列(去掉头信息行)
sed '/^>/d' sequences.fasta

# 4. 只保留头信息行并清理
sed -n '/^>/p' sequences.fasta
# -n = 安静模式(默认不输出),p = 打印匹配的行

# 5. 批量修改文件中的基因名
sed 's/gene_old/gene_new/g' annotation.gff > updated.gff

# 6. 删除行首行尾空白
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' file.txt

# 7. 在 FASTA 头信息行后添加来源信息
sed 's/^>\(.*\)/>\1 [source=metagenome]/' sequences.fasta

5.3 awk —— 文本处理"小型编程语言"

# awk 默认以空白分隔字段,$1 = 第一列,$2 = 第二列 ...
# $0 = 整行,NR = 行号,NF = 当前行的字段数

# 1. 提取 GFF 文件中所有基因的染色体和坐标
awk '$3 == "gene" {print $1, $4, $5}' annotation.gff
# $3 是第3列(特征类型),只处理值为 "gene" 的行

# 2. 计算 GFF 中每个基因的长度
awk '$3 == "gene" {print $5 - $4 + 1}' annotation.gff

# 3. 提取 TSV 文件中特定列(用 Tab 分隔)
awk -F'\t' '{print $1, $9}' annotation.gff
# -F'\t' 指定 Tab 为分隔符

# 4. 用正则过滤:只打印基因 ID 匹配 ENSG 的行
awk '/ENSG[0-9]+/' annotation.gtf

# 5. 统计 FASTA 文件中序列总长度
awk '!/^>/ {total += length($0)} END {print "Total bases:", total}' sequences.fasta

# 6. 从 BLAST 输出中过滤 e-value < 1e-10 的结果
awk '$11 < 1e-10 {print $1, $2, $11}' blast_results.txt

六、常用速查表

6.1 元字符速查

字符 含义 记忆
. 任意一个字符 点 = "随便什么都行"
\d 数字 [0-9] d = digit
\D 非数字 大写 = 取反
\w 字母/数字/下划线 w = word
\W 非单词字符 大写 = 取反
\s 空白字符 s = space
\S 非空白字符 大写 = 取反
\t Tab 制表符 t = tab
\n 换行符 n = newline

6.2 量词速查

量词 含义 等价写法
* 0 次或多次 {0,}
+ 1 次或多次 {1,}
? 0 次或 1 次 {0,1}
{n} 恰好 n 次 -
{n,} 至少 n 次 -
{n,m} n 到 m 次 -
*? 0 次或多次(懒惰模式) 尽量少匹配
+? 1 次或多次(懒惰模式) 尽量少匹配

6.3 生信高频正则速查

用途 正则表达式 说明
FASTA 头信息 ^>(.+) > 开头的行
Ensembl 基因 ID ENSG\d{11} ENSG + 11 位数字
Ensembl 转录本 ID ENST\d{11} ENST + 11 位数字
UniProt ID [A-Z][0-9][A-Z0-9]{3}[0-9] 如 P12345
DNA 序列 ^[ATCGNatcgn]+$ 只含碱基字母
蛋白质序列 ^[ACDEFGHIKLMNPQRSTVWY]+$ 20 种氨基酸
起始密码子 ATG 甲硫氨酸
终止密码子 (TAA\|TAG\|TGA) 三种终止密码子(Python 中写 (TAA|TAG|TGA),此处 \| 是 Markdown 表格转义)
GTF gene_id gene_id "([^"]+)" 引号内的基因 ID
样本编号 sample_(\d+) 下划线后的数字
FASTQ 文件名 (.+)_R([12])\.f(ast)?q(\.gz)?$ 支持 .fq/.fastq/.gz
NCBI GI 号 gi\|\d+\| 管道符分隔
染色体编号 chr([0-9XYM]+) chr1-22, X, Y, M
E-value [0-9]+\.?[0-9]*[eE][-+]?[0-9]+ 科学计数法

七、面试怎么答

Q1:什么是正则表达式?在生信中有什么用?

参考答案

正则表达式是一种描述文本模式的语言,用于在字符串中进行搜索、匹配、提取和替换。在生信中非常常用: 1. 解析 FASTA/FASTQ 头信息,提取序列 ID 2. 从 GFF/GTF 注释文件中提取 gene_id、gene_name 等属性 3. 验证 DNA/蛋白质序列格式是否合法 4. 批量处理文件名,提取样本编号 5. 配合 grep/sed/awk 做文本过滤和格式转换

常用工具:Python 的 re 模块,Shell 的 grep -Psedawk

Q2:re.search()re.match() 有什么区别?

参考答案

  • re.match() 只从字符串开头匹配,如果开头不符合就返回 None
  • re.search() 扫描整个字符串,返回第一个匹配的位置

例子:对于字符串 "hello world" - re.match(r'world', "hello world") → None(开头不是 world) - re.search(r'world', "hello world") → 匹配到 world

实际工作中:90% 的情况用 re.search(),因为我们通常不确定目标在开头还是中间。只有明确知道模式在字符串开头时才用 re.match()

Q3:贪婪匹配和懒惰匹配有什么区别?请举例说明。

参考答案

  • 贪婪(Greedy):默认行为,尽可能多地匹配字符
  • 懒惰(Lazy):加 ? 后缀,尽可能少地匹配字符

例子:对于字符串 gene_id "ENSG001"; gene_name "TP53"; - 贪婪:"(.+)" → 匹配 "ENSG001"; gene_name "TP53"(一直匹配到最后一个引号) - 懒惰:"(.+?)" → 匹配 "ENSG001"(匹配到第一个闭合引号就停)

生信中经常需要用懒惰匹配或者用 [^"]+ 来精确提取引号内的内容。

Q4:如何用正则从 GTF 文件中提取所有基因 ID?

参考答案

Shell 方法

grep -oP 'gene_id "\K[^"]+' annotation.gtf | sort -u
\K 是 Perl 正则中的"重置匹配起点",只输出后面的内容。

Python 方法

import re
with open('annotation.gtf') as f:
    gene_ids = set()
    for line in f:
        match = re.search(r'gene_id "([^"]+)"', line)
        if match:
            gene_ids.add(match.group(1))

核心正则:gene_id "([^"]+)",其中 [^"]+ 匹配引号内的所有非引号字符。

Q5:*+? 三个量词有什么区别?

参考答案

量词 含义 最少匹配次数 最多匹配次数
* 零次或多次 0 无限
+ 一次或多次 1 无限
? 零次或一次 0 1

例子:对于字符串 "ATTTG" - AT*G → 匹配 ATTTG(T 出现 0 或多次,贪婪匹配全部3个T) - AT+G → 匹配 ATTTG(T 至少出现 1 次) - AT?G → 不匹配(T 最多1次,但 A 和 G 之间有3个T,拼不成 AGATG

AT?G 只能匹配 AGATG 这两种形式。

Q6:如何验证一个字符串是否是合法的 DNA 序列?

参考答案

import re
def is_valid_dna(seq):
    """检查是否为合法DNA序列(只含ATCGN)"""
    return bool(re.fullmatch(r'[ATCGNatcgn]+', seq))

print(is_valid_dna("ATCGATCG"))    # True
print(is_valid_dna("ATCGXYZ"))     # False
print(is_valid_dna(""))            # False(+ 至少要1个)

关键点:使用 re.fullmatch()^...$ 确保整个字符串都符合,而不只是部分匹配。

Q7:如何用正则从文件名中提取样本信息?

参考答案

import re
filename = "T2D_patient03_gut_16S_R1.fq.gz"
pattern = r'(?P<project>\w+?)_patient(?P<id>\d+)_(?P<tissue>\w+)_(?P<method>\w+)_R(?P<read>[12])\.fq\.gz'
match = re.search(pattern, filename)
if match:
    info = match.groupdict()
    # {'project': 'T2D', 'id': '03', 'tissue': 'gut', 'method': '16S', 'read': '1'}

使用命名分组 (?P<name>...) 让代码更可读。


八、延伸阅读

在线工具(练习推荐)

  • regex101.com —— 最好的正则在线测试工具,支持实时高亮、解释每一部分
  • regexr.com —— 另一个好用的可视化正则测试工具

推荐学习资源

  1. Python 官方文档re 模块 → https://docs.python.org/3/library/re.html
  2. Regular-Expressions.info → https://www.regular-expressions.info/ (最全面的正则教程网站)
  3. Python 正则表达式 HOWTO → https://docs.python.org/3/howto/regex.html (Python 官方教程)

进阶主题(面试加分项)

  • 前瞻/后顾断言(Lookahead/Lookbehind):(?=...)(?<=...)
  • 非捕获组(?:...) —— 分组但不记住内容,性能更好
  • re.compile() 预编译:处理大文件时提高效率
  • re.MULTILINE 和 re.DOTALL 标志:处理多行文本
  • Python regex 第三方库:比标准 re 功能更强(支持模糊匹配等)

九、本文速记卡片

正则表达式 = 文本模式的"模板语言"

最常用5个:
  .    任意字符
  \d   数字
  +    至少1个
  *    0或多个
  ()   分组捕获

生信必记3个正则:
  ^>(.+)              → FASTA 头信息
  gene_id "([^"]+)"   → GTF 基因 ID
  [ATCG]+             → DNA 序列

Python 必记3个函数:
  re.search()   → 搜索(最常用)
  re.findall()  → 全部找出
  re.sub()      → 替换

Shell 必记3个命令:
  grep -oP '正则' 文件   → 提取
  sed 's/找/替/g' 文件   → 替换
  awk '/正则/ {动作}' 文件 → 过滤+处理