AWK:如果文件之间的两个键列匹配,则将一个文件的第 16 列添加到另一个文件的匹配行,同时保留不匹配的行

AWK:如果文件之间的两个键列匹配,则将一个文件的第 16 列添加到另一个文件的匹配行,同时保留不匹配的行

我有两个制表符分隔的文件(FileA.tsv 和 FileB.tsv)。

文件A.tsv

ID 图形 圆圈 几列... 长度
196-0 196 0 ---- 12874
195-1 195 1 ---- 12874
56-0 56 0 ---- 3349
115-1 115 1 ---- 5297

文件 A 有数百行和 12 列,此处未全部描述。 2 和 3 的每个值都不是唯一的,但它们的特定组合是唯一的。因此,event_id 是一个唯一标识符,由 2 和 3 中的值连接而成。

文件B.tsv

第 1 栏 第 2 列 第3栏 几列... 第16栏
195 1 覆盖范围 ---- CTTGCTTGAGCTGCTCTGCAA...
196 0 覆盖范围 ---- TTCTAAAGTATAAAAGCCTGTC...
196 9 覆盖范围 --- TTCTAAAGTATAAAAGCCTGTC ...
196 11 覆盖范围 --- ACATTTAAAGAATTGCTTAAG...

FileB 没有标头。

第 2 列和第 3 列与文件 A 的某些第 1 列和第 2 列相匹配。同样,第 1 列和第 2 列中的值不是唯一的,但它们的特定组合是唯一的。 FileB 中出现的所有行始终与 FileA 中的匹配,但反之则不然。

使用 awk,我想检查 FileA 的每一行是否都有 $2 和 $3 与 FileB 的 $1 和 $2 匹配,如果是,则打印完整的 FileA 行并将 FIleB 的相应 $16 值添加到该行的末尾。如果没有,则按原样打印 FIleA 行。

预期输出(文件C):

ID 图形 圆圈 几列 长度 第16栏
196-0 196 0 ---- 12874 TTCTAAAGTATAAAAGCCTGTC...
195-1 195 1 ---- 12874 CTTGCTTGAGCTGCTCTGCAA...
56-0 56 0 ---- 3349 ----
115-1 115 1 ---- 5297 ----

到目前为止,我有:

awk -F "\t" 'NR==FNR {a[$1,$2]=($16); next} ($2,$3) in a {print $0, a[$16]}' FileB.tsv FileA.tsv > FileC.tsv

此代码确实只提供了匹配的行,但是,它不会将 $16 附加到匹配行的末尾:

空的 空的 空的 空的 空的
196-0 196 0 ---- 12874
195-1 195 1 ---- 12874

如果我尝试添加 If-Else 语句:

awk -F "\t" 'NR==FNR {a[$1,$2]=($16); next} { if (($2,$3) in a) {print $0, a[$16]} else {print $0}}' FileB.tsv FileA.tsv > FileC.tsv

为了保留 FileA 的标题和不匹配的行,输出只是 FileA。

我是 awk 的新手,但是我进行了很多研究,发现了许多与此类似的操作示例,并且我的代码似乎与我见过的其他示例非常相似。

但是,我还没有找到一个示例,其中文件之间有两个对应的关键行,它们不在同一位置并且也保留了不匹配的列。

这是使用 Bash 循环在多个目录上运行的,每个目录都有自己的一组 FileA 和 FileB。在这方面没有问题,所有目录都有自己的输出 FileC,但其内容可能是错误的:

set -euo pipefail
IFS=$'\n\t'
for D in ~/Path/to/directories/with/tables/*; do
    if [ -d "${D}" ]; then
        cd "$D"
        awk -F "\t" 'NR==FNR {a[$1,$2]=($16); next} { if (($2,$3) in a) {print $0, a[$16]} else {print $0}}' *_FileB.tsv  *_FileA.tsv > "${D}".FileC.tsv
    fi
done ```

Any help or correction will be greatly appreciated.

答案1

您的 awk 脚本是一个好的开始,但您需要修复一些相当小的问题:

  1. 您需要将输出字段分隔符(OFS)设置为制表符,与 相同FS
  2. 您需要打印新的列标题
  3. 您需要打印a[$2,$3],而不是a[$16]在匹配的行上
  4. 要保留不匹配的行,您也需要打印它们,最好附加一个空字段,以便所有输出行具有相同的列数。

例如:

$ awk -F "\t" -v OFS='\t' '
  NR == FNR { a[$1,$2] = $5; next };

  FNR == 1     { c = "column 16" };
  ($2,$3) in a { c = a[$2,$3] };

  {
    print $0, c;
    c = ""
  }' FileB.tsv  FileA.tsv 
id      graph   circle  several columns...      length  column 16
196-0   196     0       ----    12874   TTCTAAAGTATAAAGCCTGTC...
195-1   195     1       ----    12874   CTTGCTTGAGCTGCTCTGCAA...
56-0    56      0       ----    3349
115-1   115     1       ----    5297

我在这里使用是a[$1,$2]=$5因为您的 FileB 示例数据只有 5 个字段。将其更改$16为您的真实数据。

这使用变量c来保存要附加的列的值。它将包含新的列名称、空字符串或要附加到匹配行中的列的值。在打印每个输出行后,它将重置为空字符串。

cat -T顺便说一句,虽然空字段通常是不可见的,但您可以通过将输出通过管道传输到- 您将^I在这些行的末尾看到(一个选项卡)来验证空字段是否已附加到不匹配的行。


从 FileB.tsv 的第一行获取列名而不是对其进行硬编码的替代版本:

$ awk -F "\t" -v OFS='\t' '
  NR == 1      { c = $5 ; next };
  NR == FNR    { a[$1,$2] = $5; next };
  ($2,$3) in a { c = a[$2,$3] };

  { print $0, c; c = "" }' FileB.tsv  FileA.tsv 

答案2

使用任何 awk:

$ cat tst.awk
BEGIN {
    FS = OFS = "\t"
}
NR == FNR {
    val = $NF
    if ( FNR == 1 ) {
        hdr = val
    }
    else {
        map[$1 FS $2] = val
    }
    next
}
{
    if ( FNR == 1 ) {
        val = hdr
    }
    else {
        key = $2 FS $3
        val = (key in map ? map[key] : "----")
    }
    print $0, val
}

$ awk -f tst.awk FileB.tsv FileA.tsv
id      graph   circle  several columns...      length  Column16
196-0   196     0       ----    12874   TTCTAAAGTATAAAGCCTGTC...
195-1   195     1       ----    12874   CTTGCTTGAGCTGCTCTGCAA...
56-0    56      0       ----    3349    ----
115-1   115     1       ----    5297    ----

如果不是输入中的最后一列,$NF则更改为。$16$16

相关内容