合并和附加两个大的分隔文件

时间:2019-05-02 10:04:08

标签: bash shell bigdata data-manipulation

我有两个巨大的逗号分隔文件。
第一个文件有2.8亿行,其后各列

first name, last name, city, state, ID, email*, phone

John,Smith,LA,CA,123123123123,johnsmith@yahoo.com,12312312
Bob,Marble,SF,CA,120947810924,,48595920
Tai,Nguyen,SD,CA,134124124124,tainguyen@gmail.com,12041284

第二个文件有4.2亿行和以下各列

first name, last name, city, state, email
John,Smith,LA,CA,johnsmith@hotmail.com
Bob,Marble,SF,CA,bobmarble@gmail.com
Tai,Nguyen,SD,CA,tainguyen@gmail.com

*其中许多字段为空

我要合并两个文件中前四列匹配的所有行。然后,如果第二个文件中的电子邮件不是空白,则用第二个文件中的电子邮件填充第一个文件中缺少的电子邮件,请不要更改它。该过程应不区分大小写。如果有很多实例具有相同的4个信息,那么只需忽略这些实例,然后仅对唯一实例进行工作即可。

结果应包含以下列,如下所示

first name, last name, city, state, ID, email, phone
John,Smith,LA,CA,123123123123,johnsmith@yahoo.com,12312312
Bob,Marble,SF,CA,120947810924,bobmarble@gmail.com,48595920
Tai,Nguyen,SD,CA,134124124124,tainguyen@gmail.com,12041284    

他们应该只打印出4列而不是1或2或3匹配的内容。我的老板坚持为此使用Bash shell脚本,我是Bash的新手。我是新手,请给我一个清晰的解释。

我已阅读并理解awk需要将信息存储到cpu内存中。但是,在这种情况下,我可以将大文件拆分为小文件并使用awk。我在线复制了一些代码,并根据需要进行更改,但是每当它填充空白电子邮件时,它也会将行定界符从逗号重新格式化为空格。我想停止,但不知道如何。请帮我解决这个问题。所有建议和答案都受到高度赞赏。

awk -F "," 'NR==FNR{a[$1,$2,$3,$4]=$5;next}{if ($6 =="") $6=a[$1,$2,$3,$4];print}' file2.txt file1.txt > file3.txt

1 个答案:

答案 0 :(得分:3)

您显示的awk方法不适用于太大的文件。它将部分文件存储在内存中。使用相同的方法,您将需要存储...或...

  • 2.8亿个条目,格式为first name, last name, city, stateID, phone
  • 4.2亿个条目,格式为first name, last name, city, stateemail

假设我们采用第一个选项,并且每个条目仅占用50个字节的内存。要存储所有2.8亿个条目,我们需要280M·50B = 14'000 MB = 14 GB。这是运行awk命令所需的绝对最小内存。实际上,由于关联数组的实现细节,该数量甚至更多。

您可以做什么

使用经典方法解决问题:

  1. sort两个文件
  2. join按文件的前四列*
  3. cut来自合并结果的所需列**

*需要进行一些预处理和后处理,因为join只能连接一列。
**由于我们必须重新安排电子邮件列cut is not sufficient。我们可以改用awk

#! /bin/bash
prefixWithKey() {
    sed -E 's/([^,]*,){4}/\L&\E\t&/' "$1"
}
sortByKeyInPlace() {
    sort -t $'\t' -k1,1 -o "$1" "$1"
}
joinByKey() {
    join -t $'\t' "$1" "$2"
}
cutColumns() {
    awk 'BEGIN{FS="\t|,\t*"; OFS=","} {print $5,$6,$7,$8,$9,$16,$11}'
}

file1="your 1st input file.csv"
file2="your 2nd input file.csv"
for i in "$file1" "$file2"; do
   prefixWithKey "$i" > "$i.tmp"
   sortByKeyInPlace "$i.tmp"
done
joinByKey "$file1.tmp" "$file2.tmp" | cutColumns > result.csv
rm "$file1.tmp" "$file2.tmp"

此脚本假定输入文件没有没有标题,并且包含没有选项卡。不管是否定义了第一个文件的电子邮件字段,我们总是从第二个文件中获取电子邮件字段。

我几乎没有测试过此脚本,因为您未提供任何示例输入。如果您遇到一些错误并分享了导致该错误的简短输入信息,我很乐意修复脚本(如果需要修复)。

理论上,脚本可以在没有临时文件的情况下编写。由于输入大小,我故意使用了临时文件。 Programs like sort may run faster on files

例如,可以通过以下方式加快该脚本的速度:

  • 同时执行对prefixWithKey的两个调用。
  • LC_ALL=C之类的命令前添加sort
  • sort添加选项,例如-S 70%

其他选择

对于大文件,将它们存储到数据库中并在其中进行处理可能会更快。甚至还有一个q工具可以在一个命令中进行这样的思考,但是从我的经验来看,它非常慢。