加入两个文件,在某些列中添加值
如何匹配另一个文件中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 eparatorFS
分隔的记录中预设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)