在使用该行的一部分索引到 file2 并查找值后,如何将值附加到 file1 中的每一行?

问题描述

我基本上有以下两个文件

$ cat file1.txt
AB,12 34 56,2.4,256,CD,23 45 67,10.8,257,EF,34 56 78,0.6,258,GH,45 67 89,58.3,259,...
$ cat file2.txt
AB,36
XY,56 99 11,3.6,15
ZQ,12 36 89,5.9,0
EF,99
GH,79
...

对于 file1.txt 中的每一行,我想使用前 3 个字段作为 file2.txt 中的索引,抓取相应的最后一个字段,并将其放入 file1.txt 中,如下所示:

cat newfile.txt
AB,36,99,79,

不能保证 file1 中的每一行都会出现在 file2 中,反之亦然,对于这种情况,newfile.txt 中上面显示的空字段是可以的。

在我的第一次尝试中,我在 while read 循环中读取 file1 中的每一行,然后在 file2 中查找相应的行,它工作正常,但速度太慢了。 file1 和 file2 各有数十万行。

有什么方法可以使用 sed 将 file1 中每行的前 3 个字段用作 file2 的索引,查找我需要的值,并将其附加到 file1 中的那一行?并且在不逐行读取 file1 的情况下这样做?

感谢任何帮助。

解决方法

使用 joinsed(用于一些预处理和后处理),并假设 | 字符未出现在任一文件中

join -a1 -t'|' \
    <(sort file1.txt | sed 's/,/|/3') \
    <(sort file2.txt | sed 's/,/|/3') |
    sed 's/,|//; s/|/,/; s/[^,]$/&,/' > newfile.txt

(使用问题中给出的输入进行测试)

它可以使用关联数组在普通的 bash 中完成,但我怀疑它是否有效。例如:

#!/bin/bash

declare -A tail

while IFS= read -r line; do
    if [[ $line =~ ([^,]*,){3} ]]; then
        tail[${BASH_REMATCH[0]}]=${line#"${BASH_REMATCH[0]}"}
    fi
done < file2.txt

while IFS= read -r line; do
    if [[ $line =~ ([^,){3} ]] && [[ -n ${tail[${BASH_REMATCH[0]}]} ]]; then
        printf '%s%s\n' "${line%?}" "${tail[${BASH_REMATCH[0]}]},"
    else
        printf '%s\n' "$line"
    fi
done < file1.txt > newfile.txt
,

使用awk:

awk -F,'FNR==NR { map[$1","$2","$3]=$4;next } { print $1","$3","$4","map[$1","$3]"," }' file2.txt file1.txt

Process file1.txt first (FNR==NR) 创建一个数组映射,以第一个、第二个和第三个逗号分隔的字段作为索引,第四个字段作为值。然后对于第二个文件,打印第一个、第二个、第三个和第四个字段以及第一个索引的地图数组的内容,用逗号分隔。

,

这将非常有效地在每个 Unix 机器上的任何 shell 中使用任何 awk,并且不依赖于输入中不存在的任何字符:

$ awk '
    BEGIN { FS=OFS="," }
    { key = $1 FS $2 FS $3 }
    NR==FNR { map[key] = $4; next }
    { $5 = map[key] }
1' file2 file1
AB,12 34 56,2.4,256,36,CD,23 45 67,10.8,257,EF,34 56 78,0.6,258,99,GH,45 67 89,58.3,259,79,