Linux - 合并两个 CSV 文件

12

我有两个CSV文件:

file_1 columns: id,user_id,message_id,rate
file_2 columns: id,type,timestamp

这两个文件之间的关系是 file_1.message_id = files_2.id

我想创建一个第三个文件,该文件将具有以下列:

file_1.id,file_1.user_id,file_1.message_id,file_1.rate,file_2.timestamp

有没有关于如何在Linux中实现这个的想法?


可能是重复问题:https://dev59.com/UVjUa4cB1Zd3GeqPOBnW - Dan Fego
它们不同,在那里的答案中,它们附加行而不是列。 - Ran
你想在最终文件的每一列前加上 file_1 吗? - jaypal singh
是的,在最后输出中,应该将文件2的1列(时间戳)添加到文件1中,使其看起来像file_1。 - Ran
4个回答

11
您可以这样使用 join 命令:
join -t, -1 3 -2 1 -o 1.1 1.2 1.3 1.4 2.3 <(sort -t, -k 3,3 file1) <(sort file2)

此操作首先对文件进行排序(以第3个字段为关键字对file1进行排序),然后使用file1的第3个字段和file2的第1个字段进行连接。最后输出所需的字段。


我得到了以下错误信息:join: 文件2未按排序顺序排列,join: 文件1未按排序顺序排列。 - Ran
1
即使您按照dogbanes的解决方案运行它(sort <file1>),只有<file1>被更改,包括sort! - David Chan
这里可能有一个bug...据我所知,只有在文件按连接键排序的情况下,join才能起作用。由于你在file1上调用了sort函数,它将按照第一列进行排序,而不是按照排序键排序。 - Pradeep Gollakota
2
这个命令可能应该改为以下命令... join -t -1 3 -2 1 -o 1.1 1.2 1.3 1.4 2.3 <(sort -t $',' -k 3,3 file1) <(sort file2) 以便按正确的连接键进行排序。 - Pradeep Gollakota
@PradeepGollakota 差不多了。你在 join -t, ... 中忘记了逗号。此外,我认为 sort -t $','sort -t, 是等价的,但后者更短。 - myrdd

4

看起来需要使用 SQLite。使用 SQLite shell

 create table f1(id,user_id,message_id,rate);
 create table f2(id,type,timestamp);

 .separator ,
 .import 'file_1.txt' f1
 .import 'file_2.txt' f2

 CREATE INDEX i1 ON f1(message_id ASC); -- optional
 CREATE INDEX i2 ON f2(id ASC);         -- optional

 .output 'output.txt'
 .separator ,

 SELECT f1.id, f1.user_id, f1.message_id, f1.rate, f2.timestamp
   FROM f1
   JOIN f2 ON f2.id = f1.message_id;

 .output stdout
 .q

请注意,如果在单行逗号数量上存在单个错误,则导入阶段将失败。您可以在脚本开头使用.bail on来防止其余脚本运行。
如果您想要未匹配的id,可以尝试:
SELECT f1.* FROM f1 LEFT JOIN f2 on f2.id = f1.message_id WHERE f2.id IS NULL

这将选择每一行来自f1,其中没有发现在f2中对应的行。


我最初犹豫是否应该评论“使用适当的数据库”,但这似乎可以使用数据库作为临时机制完成工作。+1 - Dan Fego
每个文件大约有7000万条记录,我认为对于数据库来说可能会很“困难”。 - Ran
@Ran:如果你创建索引,那么绝对不会。CREATE INDEX i1 ON f1(message_id); CREATE INDEX i2 ON f2(id); 你可以在.import阶段之后创建索引。如果这些列的值是唯一的,你可以在创建表语句中将它们声明为PRIMARY KEY,那么就不需要使用索引了。使用.bail on在最轻微的错误时退出。 - Benoit
@Ran:但请注意,如果您有大量数据,使用中间数据库文件可能更有效。运行sqlite foo.db而不是sqlite - Benoit

0

使用 awk,您可以尝试类似以下的操作 -

awk -F, 'NR==FNR{a[$3]=$0;next} ($1 in a){print a[$1]","$3 > "file_3"}' file_1 file_2

测试:

[jaypal:~/Temp] cat file_1     # Contents of File_1
id,user_id,message_id,rate
1,3334,424,44

[jaypal:~/Temp] cat file_2     # Contents of File_2
id,type,timestamp
424,rr,22222

[jaypal:~/Temp] awk -F, 'NR==FNR{a[$3]=$0;next} ($1 in a){print a[$1]","$3 > "file_3"}' file_1 file_2

[jaypal:~/Temp] cat file_3     # Contents of File_3 made by the script
1,3334,424,44,22222

我不需要输出文件中的“file_1.”,这只是为了显示我需要每列来自哪个文件。 - Ran
好的,谢天谢地,因为情况有点混乱。我已经更新了答案。希望能帮到你! :) - jaypal singh
@Ran 我用样本数据进行了测试,看起来它可以正常工作。如果你遇到任何问题或不理解任何内容,请告诉我,我会带领你完成。 - jaypal singh
如果每个文件有大约80M条记录,awk是正确的解决方案吗? - Ran
基本上就是看你想要投入多少时间。一个好的解决方案是用编译语言编写高效的程序,而不是解释性语言。你可以试一试并查看性能。随着机器速度的提高,解释性/脚本语言的性能也不错。 - jaypal singh
使用 join 怎么样?有什么想法如何在那里实现它吗? - Ran

0
你可以尝试这样做:
1. 将所有行都改为以关键字开头:
awk -F',' { print $3 " file1 " $1 " " $2 " " $4 } < file1 >  temp
awk -F',' { print $1 " file2 " $2 " " $3 }        < file2 >> temp

现在这些行看起来像:
message_id file1 id user_id rate
id file2 type timestamp
  1. 按照第一列和第二列对 temp 进行排序。现在相关的行相邻,首先是 file1

    sort -k 1,1 -k 2,2 < temp > temp2

  2. 运行 awk 读取这些行。在 file1 行中保存字段,在 file2 行中打印它们。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接