Bash中如何结合常量与正则批量匹配替换文件名?
问题描述
现有一批测序数据文件,原文件名格式如下:
S2EC1_DKDL220005480-2a-AK13554-7UDI265_HHJ2MCCX2_L8_1.fq.gz S2EC1_DKDL220005480-2a-AK13554-7UDI265_HHJ2MCCX2_L8_2.fq.gz S2EC2_DKDL220005480-2a-5UDI249-7UDI265_HHJ2MCCX2_L8_1.fq.gz S2EC2_DKDL220005480-2a-5UDI249-7UDI265_HHJ2MCCX2_L8_2.fq.gz S2EC11_DKDL220005480-2a-5UDI251-5UDI1063_HHJ2MCCX2_L8_1.fq.gz S2EC11_DKDL220005480-2a-5UDI251-5UDI1063_HHJ2MCCX2_L8_2.fq.gz
需要重命名为:
S2EC1_R1.fastq.gz S2EC1_R2.fastq.gz S2EC2_R1.fastq.gz S2EC2_R2.fastq.gz S2EC11_R1.fastq.gz S2EC11_R2.fastq.gz
已知文件名包含固定片段DKDL220005480-2a-和_HHJ2MCCX2_L8,两者之间为可变内容。
已尝试分步执行Bash命令处理:
for x in *; do mv $x ${x/DKDL220005480-2a-/}; done for x in *; do mv $x ${x/_HHJ2MCCX2_L8_/_R}; done
得到中间文件名:
S2EC1_AK13554-7UDI265_R1.fq.gz S2EC1_AK13554-7UDI265_R2.fq.gz S2EC2_5UDI249-7UDI265_R1.fq.gz S2EC2_5UDI249-7UDI265_R2.fq.gz S2EC11_5UDI251-5UDI1063_R1.fq.gz S2EC11_5UDI251-5UDI1063_R2.fq.gz
但执行以下命令移除中间可变部分时出错:
for x in *; do mv $x ${x/_(.+)_/}; done
错误信息:
mv: 'S2EC1_AK13554-7UDI265_R1.fq.gz' and 'S2EC1_AK13554-7UDI265_R1.fq.gz' are the same file mv: 'S2EC1_AK13554-7UDI265_R2.fq.gz' and 'S2EC1_AK13554-7UDI265_R2.fq.gz' are the same file mv: 'S2EC2_5UDI249-7UDI265_R1.fq.gz' and 'S2EC2_5UDI249-7UDI265_R1.fq.gz' are the same file mv: 'S2EC2_5UDI249-7UDI265_R2.fq.gz' and 'S2EC2_5UDI249-7UDI265_R2.fq.gz' are the same file mv: 'S2EC11_5UDI251-5UDI1063_R1.fq.gz' and 'S2EC11_5UDI251-5UDI1063_R1.fq.gz' are the same file mv: 'S2EC11_5UDI251-5UDI1063_R2.fq.gz' and 'S2EC11_5UDI251-5UDI1063_R2.fq.gz' are the same file
错误原因
Bash的参数扩展${parameter/pattern/string}使用的是通配符(glob)匹配,而非正则表达式语法。你写的_(.+)_会被当作字面字符串匹配,而文件名中不存在该字符串,因此替换操作未执行,导致mv命令的源文件和目标文件完全相同,触发错误。
解决方案
方案一:修正中间文件的重命名命令
针对当前的中间文件名,用Bash通配符匹配替换中间可变部分:
for x in *.fq.gz; do # 替换第一个_到第二个_之间的所有内容为单个_ temp=${x/_*_/_} # 将.fq.gz后缀替换为.fastq.gz new_name=${temp/.fq.gz/.fastq.gz} mv "$x" "$new_name" done
解释:
_*_是通配符模式,匹配第一个_开始到下一个_结束的所有内容(即中间的可变部分),替换为单个_后得到S2EC1_R1.fq.gz格式。- 最后替换后缀,得到目标文件名。
方案二:一步到位直接处理原文件
无需分步操作,直接从原文件名提取前缀和读段编号生成目标文件名,效率更高:
for file in *.fq.gz; do # 提取前缀(第一个_DKDL220005480-2a-之前的部分) prefix=${file%%_DKDL220005480-2a-*} # 提取读段编号(最后一个_之后的数字部分) read_num=${file##*_} read_num=${read_num%.fq.gz} # 拼接目标文件名并执行重命名 mv "$file" "${prefix}_R${read_num}.fastq.gz" done
解释:
${file%%_DKDL220005480-2a-*}:从文件名末尾向前删除最长匹配的_DKDL220005480-2a-*,得到前缀如S2EC1。${file##*_}:从文件名开头向后删除最长匹配的*_,得到1.fq.gz或2.fq.gz,再通过${read_num%.fq.gz}去掉后缀得到数字1或2。- 最终拼接出目标文件名
${prefix}_R${read_num}.fastq.gz。
内容的提问来源于stack exchange,提问作者Andreanna
相关产品推荐
相关产品推荐

