我有两个制表符分隔的文件(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 脚本是一个好的开始,但您需要修复一些相当小的问题:
- 您需要将输出字段分隔符(
OFS
)设置为制表符,与 相同FS
。 - 您需要打印新的列标题
- 您需要打印
a[$2,$3]
,而不是a[$16]
在匹配的行上 - 要保留不匹配的行,您也需要打印它们,最好附加一个空字段,以便所有输出行具有相同的列数。
例如:
$ 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