我有一个 4 列制表符分隔的文件,最后一列有时有重复项。这是该文件的摘录:
chr7 116038644 116039744 GeneA
chr7 116030947 116032047 GeneA
chr7 115846040 115847140 GeneA
chr7 115824610 115825710 GeneA
chr7 115801509 115802609 GeneA
chr7 115994986 115996086 GeneA
chrX 143933024 143934124 GeneB
chrX 143933119 143934219 GeneB
chrY 143933129 143933229 GeneC
对于该列中的每组重复值,我想将它们转换为类似的内容(而不真正触及该列中的非重复值):
chr7 116038644 116039744 GeneA-1
chr7 116030947 116032047 GeneA-2
chr7 115846040 115847140 GeneA-3
chr7 115824610 115825710 GeneA-4
chr7 115801509 115802609 GeneA-5
chr7 115994986 115996086 GeneA-6
chrX 143933024 143934124 GeneB-1
chrX 143933119 143934219 GeneB-2
chrY 143933129 143933229 GeneC
我如何使用awk
or sed
or Bashfor
循环来做到这一点?
答案1
尝试这个
awk -F'\t' -v OFS='\t' '{$4=$4 "-" (++count[$4])}1' file.tsv
这会将第四个字段的每个值的出现存储在计数器数组中count
(其中第四个字段的值用作“索引”),并将该计数器的预递增值附加到第四个字段,并用短跑。
上面的“简单”示例有一个缺点:它甚至会为第 4 列中仅在文件中出现一次的值添加消歧编号。为了抑制这种情况,可以使用以下双遍方法(通过将命令分成两行以\
提高可读性):
awk -F'\t' -v OFS='\t' 'NR==FNR{f[$4]++}\
NR>FNR{if (f[$4]>1) {$4=$4 "-" (++count[$4])}; print}' file.tsv file.tsv
注意要处理的文件已注明两次作为参数,因此将被读取两次。
- 第一次读取时(由 表示
FNR
,每个文件行计数器,等于NR
,全局行计数器),我们只需计算第 4 列的每个不同值在文件中出现的频率,并将其存储在数组 中f
。 - 第二次读取文件时,我们像“简单”方法一样执行实际的文本处理,并将出现计数器附加到第 4 列,但前提是在第一次传递中找到的出现总数大于 1。
这种方法避免了缓冲整个文件,如果文件非常大,这可能是一个优点。处理时间当然会更长,因为文件被读取了两次。
作为一般规则,很少需要使用 shell 循环进行文本处理,因为awk
例如可以以更有效的方式自行执行循环操作。
答案2
假设您的输入文件按第四列分组,如示例所示:
$ cat tst.awk
$NF != prev {
prt()
cnt = 0
prev = $NF
}
{ rec[++cnt] = $0 }
END { prt() }
function prt() {
for (i=1; i<=cnt; i++) {
print rec[i] (cnt > 1 ? "-"i : "")
}
}
。
$ awk -f tst.awk file
chr7 116038644 116039744 GeneA-1
chr7 116030947 116032047 GeneA-2
chr7 115846040 115847140 GeneA-3
chr7 115824610 115825710 GeneA-4
chr7 115801509 115802609 GeneA-5
chr7 115994986 115996086 GeneA-6
chrX 143933024 143934124 GeneB-1
chrX 143933119 143934219 GeneB-2
chrY 143933129 143933229 GeneC
答案3
这仅附加“-数字如果其值不唯一,则将其添加到指定的(目标)字段(示例中的第四个字段)。它还处理输入未按目标列排序的情况,并适用于任意数量的输入列。
由于以下 AWK 脚本需要按目标字段对输入进行排序,因此我们使用管道对原始行进行编号,按(现在)第五个字段(第一个是前置数字)对它们进行排序,将后缀附加到非-第五个字段的唯一值,将行恢复到初始排序并删除前置数字:
nl file | sort -b -t '<TAB>' -k5,5 -k1n,1n | awk -F '\t' -v OFS='\t' -v kf=5 '
function prn () {
for (i = 1; i <= nfl; i++) {
if (i == kf)
printf("%s", prc[i] ( sw || cnt[prc[i]] ? "-"++cnt[prc[i]] : ""))
else
printf("%s", prc[i])
printf("%s", (i == nfl ? ORS : OFS))
}
}
NR > 1 {
sw = ($kf == prc[kf])
prn()
}
{
nfl = split($0, prc)
}
END {
if (NR > 0)
prn()
} ' | sort -k1n,1n | cut -f 2-
这个 AWK 脚本的要点是打印以前的检查该行的kf
第一个字段是否等于当前行的字段或者它的kf
第一个字段是否已经出现至少一次。在这两种情况下,kf
都会打印第 th 字段以及附加到该字段的次数。
确保进行调整-v kf=5
(和-k5,5
sort
键)以反映要消除歧义的列的实际位置。
给定此示例(您的示例,其中包含已打乱的行和添加的列)file
:
chr7 116038644 116039744 GeneA foo
chrX 143933024 143934124 GeneB foo
chr7 116030947 116032047 GeneA foo
chr7 115824610 115825710 GeneA foo
chrY 143933129 143933229 GeneC foo
chr7 115994986 115996086 GeneA foo
chrX 143933119 143934219 GeneB foo
chr7 115801509 115802609 GeneA foo
chr7 115846040 115847140 GeneA foo
输出将是:
chr7 116038644 116039744 GeneA-1 foo
chrX 143933024 143934124 GeneB-1 foo
chr7 116030947 116032047 GeneA-2 foo
chr7 115824610 115825710 GeneA-3 foo
chrY 143933129 143933229 GeneC foo
chr7 115994986 115996086 GeneA-4 foo
chrX 143933119 143934219 GeneB-2 foo
chr7 115801509 115802609 GeneA-5 foo
chr7 115846040 115847140 GeneA-6 foo
答案4
一个简单的两遍awk
命令:
$ awk -F '\t' '
BEGIN { OFS=FS }
pass == 1 { count[$4]++; next }
count[$4] > 1 { $4 = $4 "-" ++number[$4] }; 1' pass=1 file pass=2 file
chr7 116038644 116039744 GeneA-1
chr7 116030947 116032047 GeneA-2
chr7 115846040 115847140 GeneA-3
chr7 115824610 115825710 GeneA-4
chr7 115801509 115802609 GeneA-5
chr7 115994986 115996086 GeneA-6
chrX 143933024 143934124 GeneB-1
chrX 143933119 143934219 GeneB-2
chrY 143933129 143933229 GeneC
这假设输入文件是制表符分隔的。-F '\t'
如果情况并非如此,请修改或删除。
第一次遍历该文件时,关联数组 中将count
填充每个基因名称在第四列中出现的次数。
在第二次通过文件时,如果基因名称在数据集中多次出现(在第一次通过文件时),则会在基因名称末尾添加破折号和数字。
添加的数字是另一个计数器,也由基因名称作为键控,就像在counter
数组中一样。