使用 awk/join 基于列连接条目

使用 awk/join 基于列连接条目

我有两个用竖线分隔的文件,并且两个文件中的第 1 列+第 2 列可能匹配,或者一个文件可能包含该条目,而另一个文件则没有。假设我要使用管道 '|' 得出的匹配键等于 $1"-"$2作为FS。

文件1

1111|AAA|foo|50
1111|BBB|foo|30
2222|BBB|foo|10

文件2

1111|AAA|bar|10
1111|CCC|bar|20
3333|AAA|bar|40

第一个条目所需的输出如下(我有这个工作)

1111|AAA|50|10

对于第二个条目file1(如果两个文件中都没有匹配的column1+column2,则将foo缺少的条目替换为0。反之亦然)

1111|BBB|30|0

对于文件 2 中但不在文件 1 中的条目键(列 1+列 2)(这是文件 2 预期输出的条目 3)

3333|AAA|0|40

因此,所需的输出整体格式是列出两个文件中由 column1+column2 表示的所有唯一键。第三列条目是文件 1 第 4 列中的值(如果文件 1 中不存在值,则为 0),输出中的第四列为文件 2 的第 4 列中的值(如果文件 2 中不存在值,则为 0) )。

我做了很多研究并尝试了很多事情,但如果使用以下命令,如果 file2 中存在 column1+column2 对,但 file1 中不存在,则我的值不会输出:

join -t"|" -e0 -a1 -a2 -o 1.2,1.3,1.5,2.5 <(<file1 awk -F"|" '{print $1"-"$2"|"$0}' | sort -k1,1) <(<file2 awk -F"|" '{print $1"-"$2"|"$0}' | sort -k1,1)

如果 file1 中有一个 column1+column2 匹配但 file2 中没有,上面的情况会给出预期的输出,并为不存在的匹配附加 0...我怎样才能让它适用于所有场景?

上面的命令将通过在两个文件的第 1 列(column1+column2)中添加一个键来进行一些过程替换,然后基于该新键进行连接。 -e0 如果该键存在于 file1 但不存在于 file2 中,则会添加 0。我怎样才能让它涵盖以下情况:新密钥(column1-column2)存在于文件 2 但不存在于文件 1 中?

答案1

按照你的方法,你必须使用join两次(或改变你的方法,通过一次join调用来完成它):

  • 打印公共线和不可配对的file1线join -t'|' -e0 -a1 -o 1.2,1.3,1.5,2.5 <(<file1 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1) <(<file2 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1)
  • 打印不可配对的file2join -t'|' -e0 -v2 -o 2.2,2.3,1.5,2.5 <(<file1 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1) <(<file2 awk -F'|' '{print $1"-"$2"|"$0}' | sort -t'|' -k1,1)

您可以通过一次awk调用执行相同的操作,存储$4在由 eg 索引的两个数组中$1|$2,然后在END块中迭代每个数组索引,比较它们并相应地打印:

awk -F'|' 'NR==FNR{z[$1"|"$2]=$4;next}{x[$1"|"$2]=$4}
END{for (j in x){if (!(j in z)){print j, "0", x[j]}};
for (i in z){if (i in x){print i, z[i], x[i]} else {print i, z[i], "0"}}
}' OFS="|"  file1 file2

答案2

以下将|两个文件中的第一个替换为@(使用文件中其他地方未出现的字符),执行join,然后将 更改回@原始|。这样,我们就创建了一个新的|分隔连接字段,其中包含原始文件中的第 1 列和第 2 列。

join -t'|' -e0 -a1 -a2 -o0,1.3,2.3 \
    <( sed 's/|/@/' file1 | sort )  \
    <( sed 's/|/@/' file2 | sort ) |
tr '@' '|'

在输出字段规范 ( -o) 中,零表示连接字段,任一文件中的第 3 列实际上是原始数据的第 4 列。

对于给定的输入文件,这会生成

1111|AAA|50|10
1111|BBB|30|0
1111|CCC|0|20
2222|BBB|10|0
3333|AAA|0|40

答案3

另一种awk方法:

awk -F'|' 'NR==FNR{f1[$1FS$2]=$NF;next} {f2[$1FS$2]=$NF} 
    END{for (x in f1){print x,f1[x],f2[x]?f2[x]:0; delete f2[x]};
        for (y in f2) print y, 0, f2[y]
}' file[12] OFS='|'

解释:

  • NR==FNR{f1[$1FS$2]=$NF;next},这将仅针对 file1 运行,并且使用组合键将在名为(将替换为awk 的数组中$1FS$2存储最后一列值$NFf1FS|F产量S运算符)。
  • {f2[$1FS$2]=$NF},与上面相同,但这只会针对 file2 运行
  • for (x in f1){print x,f1[x],f2[x]?f2[x]:0; delete f2[x]},在数组中循环f1并打印 key ( x),它在 file1 中的值f1[x],如果 file2 中存在相同的 file1 key,则也打印它,否则打​​印0(使用三元条件f2[x]?f2[x]:0),之后我们还从 file2 中删除相同 key 的记录delete f2[x]
  • for (y in f2) print y, 0, f2[y],现在 arrayf2包含仅存在于 file2 中的记录,因此我们打印它们的键 ( y),0因为它们不存在于 file1 中,并且它们的值存在于 file2 中f2[y]

相关内容