加入两个文件,在某些列中添加值

如何匹配另一个文件中2列的值后,如何添加另外2个值,就像VLOOKUP一样?

以下示例。 file1中第6列和第7列的值与file2中的第1列和第2列匹配时,将在file1中添加第9列和第10列,其中第3列和第4列的值来自file2。

FILE1.TXT

1 1 1 1 1 5 9 1 2 2 2 2 2 7 8 2 3 3 3 3 3 7 7 3 4 4 4 4 4 8 6 4 

FILE2.TXT

 5 9 AB 8 6 EF 7 7 GH 7 8 CD 

output.txt的

 1 1 1 1 1 5 9 1 AB 2 2 2 2 2 7 8 2 CD 3 3 3 3 3 7 7 3 GH 4 4 4 4 4 8 6 4 EF 

谢谢,

使用awk

 awk 'NR==FNR{ seen[$1FS$2]=$3FS$4; next } { print $0, seen[$6FS$7] }' file2 file1 

并从输出中删除空行:

 awk 'NR==FNR{ seen[$1FS$2]=$3FS$4; next } NF{ print $0, seen[$6FS$7] }' file2 file1 

或者一点点空白和合理的变量名称对可读性有很大帮助。 此外,利用数组键中的逗号

 awk ' NR == FNR {value[$1,$2] = $3 OFS $4; next} {print $0, value[$6,$7]} ' file2.txt file1.txt 

  • 当第一个记录由awk读取时, NR设置为1,并且对于在单个或多个输入文件中读取的每个下一个记录递增,直到所有读取完成。
  • awk读取第一个记录时, FNR设置为1,当前文件读取每个下一个记录时FNR设置为1,如果有多个输入文件,则重置为1。
  • 因此NR == FNR始终是一个真实条件,此后的块将仅对第一个文件执行操作。

  • seen是一个关联的awk数组,其中列$ 1和列$ 2的键组合,列$ 3和列$ 4的值。

  • next标记跳过执行其余命令,除了第一个,它们只会实际执行下一个文件。

  • NF ; 在字段已知并用Field S eparator FS分隔的记录中预设N个字节; 因此,列之间的FS用于完整的字段分隔符,或者您可以在数组中使用逗号。

  • 所以这个NF{ print $0, seen[$6FS$7] } ,打印文件1中的当前记录$0 ,以及与$ 6列匹配的值以及当不是空行时看到的数组中出现的$ 7列。

我知道你没有要求数据库解决方案,但如果你碰巧有一个MySQL服务器,这里是如何做到的:

 create table file1 (c1 int, c2 int, c3 int, c4 int, c5 int, c6 int, c7 int, c8 int); create table file2 (c1 int, c2 int, c3 char, c4 char); load data infile 'file1' into table file1 fields terminated by ' '; load data infile 'file2' into table file2 fields terminated by ' '; select f1.*, f2.c3, f2.c4 from file1 as f1 join file2 as f2 on f1.c6 = f2.c1 and f1.c7 = f2.c2 order by f1.c1; 

(我不得不剥去空白行)

结果:

 +------+------+------+------+------+------+------+------+------+------+ | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c3 | c4 | +------+------+------+------+------+------+------+------+------+------+ | 1 | 1 | 1 | 1 | 1 | 5 | 9 | 1 | A | B | | 2 | 2 | 2 | 2 | 2 | 7 | 8 | 2 | C | D | | 3 | 3 | 3 | 3 | 3 | 7 | 7 | 3 | G | H | | 4 | 4 | 4 | 4 | 4 | 8 | 6 | 4 | E | F | +------+------+------+------+------+------+------+------+------+------+ 4 rows in set (0,00 sec) 

回应@Jos的回答:sqlite

 db=$(mktemp) sqlite3 "$db" <<'END' create table f1 (v1 text,v2 text,v3 text,v4 text,v5 text,v6 text,v7 text,v8 text); create table f2 (v1 text,v2 text,v3 text,v4 text); .separator " " .import file1.txt f1 .import file2.txt f2 select f1.*, f2.v3, f2.v4 from f1,f2 where f1.v6=f2.v1 and f1.v7=f2.v2; END rm "$db" 

或以几乎一行的方式:

 sqlite3 -separator " " <<'END' create table f1 (v1, v2, v3, v4, v5, v6, v7, v8 ); create table f2 (v1, v2, v3, v4); .import file1.txt f1 .import file2.txt f2 select f1.*, f2.v3, f2.v4 from f1,f2 where f1.v6=f2.v1 and f1.v7=f2.v2; END 

bash:我冒昧从文件中删除空白行。

 declare -A keys while read -r k1 k2 value; do keys[$k1,$k2]=$value done < file2.txt while read -ra fields; do key="${fields[5]},${fields[6]}"; echo "${fields[*]} ${keys[$key]}" done < file1.txt 
 1 1 1 1 1 5 9 1 AB 2 2 2 2 2 7 8 2 CD 3 3 3 3 3 7 7 3 GH 4 4 4 4 4 8 6 4 EF 

这将有效,虽然我很确定有人会提出一个更好的单线程awk解决方案。

 cp file1.txt output.txt && while read -r file2_line; do # Empty line --> continue [[ -z "$file2_line" ]] && continue # Find matching line file1_matching_line=$(grep -n "$(echo "$file2_line" | cut -d' ' -f 1,2)" <(cut -d' ' -f6,7 output.txt) | grep -Po "^[0-9]+"); # no find? continue! [[ ! $? -eq 0 ]] && continue # Add the fields 3 and 4 of file2 to the end of the matching line of output.txt echo "$file1_matching_line" | while read -r ml; do sed -i "${ml}s/$/ $(echo "$file2_line" | cut -d' ' -f 3,4)/" output.txt done done < file2.txt && cat output.txt 

魔术发生在这一行:

 file1_matching_line=[...] 

找到文件2的所有字段1和2的行号( -n

 $(echo "$file2_line" | cut -d' ' -f 1,2) 

在output.txt中,它是file1.txt的副本

 <(cut -d' ' -f6,7 output.txt)