Unix/bash:for 循环与 grep 折叠行

Unix/bash: For loops with grep to collapse rows

我有一个制表符分隔的文件 A,如下所示:

nameA GO:0005737 细胞质
nameB GO:0005875 微管相关复合体
nameB GO:0005884 肌动蛋白丝
nameC GO:0005737 细胞质
nameC GO:0005856 细胞骨架
nameC GO:0005524 ATP 绑定

..

第一列是基因名称,第二列是 GO id,第三列是对该 id 的描述。第一列中的每个标识符可以有一行或几行。

我想创建一个新文件,其中每个基因名称只有一行,所有相关的 GO 术语都在第二列,描述在第三列:

nameA GO:0005737 细胞质
nameB GO:0005875,GO:0005884, 微管相关复合体, 肌动蛋白丝
nameC GO:0005737, GO:0005856, GO:0005524 细胞质,细胞骨架,ATP 结合
...

...并且 GO id 的顺序遵循描述术语的顺序,即每行中的第一个 GO id 对应于第一个描述术语。

我尝试获取所有基因名称的唯一列表,然后 运行 对每个基因名称进行 for 循环 grepping,删除 GO 列并用逗号替换换行符,然后在结束。

cut -f1 文件A | uniq > 标识符

for name in `cat identifiers`
做
    grep "$name" 文件A |切-f2 | tr '\n' ',' | sed 's/$/\n/' >> GOs_collapsed
完成

在此之后,我计划对第三列执行相同的操作,然后使用粘贴将两者与标识符文件放在一起。

但是,上面的 bash 脚本不起作用。 GOs_collapsed 中的输出只是 GO:s 的列表,就像之前一样。

GO:0005737
GO:0005875
GO:0005884
.. 

有什么想法吗?

假设输入在每行的第一个字段上排序,这应该可以满足您的要求。

$ cat group.awk
BEGIN {
    FS=OFS="\t"
}

function printline(last, col, cols) {
    printf last
    for (i = 2; i <= cols; i++) {
        printf OFS"%s", col[i]
    }
    printf ORS
}

 != last {
    if (last) {
        printline(last, col, cols)
    }

    # Reset last and our accumulated fields.
    last=
    split("", col)
}

 == last {
    cols = (cols > NF) ? cols : NF
    for (i = 2; i <= NF; i++) {
        col[i] = col[i] (col[i]?",":"") $i
    }
    next
}

END {
    printline(last, col, cols)
}
$ awk -f group.awk fileA

您可以使用 awk one liner 来完成,如下所示:

awk 'BEGIN {
       FS=OFS="\t"
     } 
     { if (a[] == "") { 
          a[]=; b[]= 
       } else { 
           a[]=a[] "," ; b[]=b[] "," ;
       } 
     } END { 
         for (i in a) 
             print i "\t" a[i] "\t" b[i] 
     }' myfile.txt

读取一个密钥的所有条目,并在看到新密钥时打印收集的输出。这要求一个键的所有条目都相邻,这可以通过对输入进行排序来轻松实现。

IFS=$'\t'
sort fileA |
while read -r key go desc; do
    if [ "$key" != "$prev" ] && [ "$prev" != "" ]; then
        printf '%s\t%s\t%s\n' "$prev" "${gos#,}" "${descs#,}"
        gos=""
        descs=""
    fi
    gos="$gos,$go"
    descs="$descs,$desc"
    prev="$key"
done
printf '%s\t%s\t%s\n' "$key" "${gos#,}" "${descs#,}"

构造 ${var#prefix} returns var 的值并删除任何 prefix。允许并期望一个前导逗号简化了主要流程,因此我们不必在第一轮中为新密钥特例。

还要注意进入 while 循环的管道,它避免了临时文件和讨厌的 for 循环。

根据您目前的情况:

cut -f1 -d' '  fileA | uniq | while read name; do
    awk -v name="$name" ' == name {print }' fileA | paste -s -d','  > GOs
    echo "$name    $(awk -v name="$name" ' == name {print }' fileA | paste -s -d',' | paste GOs -)"
done

如果字段由制表符而不是空格分隔,请将 cut -d1 -d' ' 更改为 cut -f1

您可以通过编程方式这样做。

for name in `cut  -d' ' -f 1 file.txt | uniq`
do
    line="$name\t"
    grepVal=`grep "$name" file.txt`
    for val in `grep "$name" file.txt | cut -d' ' -f6`
    do
            line="$line$val, "
    done
    line="$line\t"
    for desc in `grep "$name" file.txt | cut -d' ' -f 11-36`
    do
            line="$line$desc, "
    done
    echo $line >> GOs_collapsed
done 

输出

nameA   GO:0005737,     cytoplasm,
nameB   GO:0005875, GO:0005884,     microtubule, associated, complex, actin, filament,
nameC   GO:0005737, GO:0005856, GO:0005524,     cytoplasm, cytoskeleton, ATP, binding,