我有一个非常大的csv文件,我想将其拆分为多个较小的文件,以使大文件中ID列(csv中第二列)具有相同值的所有条目最终都位于同一文件中。但是,在每个较小的文件中,我还需要有50个不同的ID。
我有执行此操作的代码,但是对于1 gig文件,大约需要15-20分钟。有这样做的有效方法吗?
这就是我现在拥有的:
awk -F, '{if(NR > 1) {print >> $2"_backfill_tmp.csv"; close($2"_backfill_tmp.csv")}}' $input_file
counter=0
for file in *"_backfill_tmp.csv"
do
file_name=${input_file%.*}"_backfill2_part_"$PART_NUMBER".csv"
cat "$file" >> "$file_name"
rm "$file"
(( counter++ ))
if (( $counter % 50 == 0 )) ; then
(( PART_NUMBER++ ))
fi
done
awk命令根据第2列的值将每一行写入单独的文件(忽略第一行即标头),以便具有相同ID值的每一行最终都位于同一文件中。每次关闭文件都是因为遇到Too many files open error
,并且无法在计算机上设置ulimit
。但是,此过程仅需15秒钟左右,因此无需担心。
然后我遍历每个临时创建的文件,并将它们写入单独的文件,直到$counter
达到50(即将50个文件合并在一起)。现在,这是需要很多时间的地方。我猜是因为有很多单独的ID文件,所以一个一个地打开它们并合并它们需要很长时间。
我是awk初学者,所以我很确定我的代码效率不高。无论如何,我可以更快地完成整个过程吗?
答案 0 :(得分:5)
您可以使用以下脚本。我没有使用close
,因为现在打开的文件只有#uniqueIDs / 50,而不是#uniqueIDs。
awk -F, 'NR > 1 {
if (!($2 in mapIdToPart)) {
if (uniqueIds % 50 == 0) {
maxPart++;
}
mapIdToPart[$2] = maxPart;
uniqueIds++;
}
print >> "part"mapIdToPart[$2]".csv";
}' input.csv
这将创建文件part#.csv
,其中#
是当前零件的编号。输入文件不必排序。具有相同ID的行将进入同一部分。每个部分中的行顺序与输入文件中的行顺序相对应。每个部分都有50个(对于最后一部分,更少)(唯一的ID)。
当您的输入文件按ID排序时,您可以加快脚本的运行速度,因为这样就不需要映射mapIdToPart
,并且每个生成的部分都可以一次性编写。
顺序可以是字母,数字,...,没关系。在这里,我假设排序后的文件不再包含标题。如果仍然有标题,请在NR > 1
脚本的开头添加awk
。
awk -F, '{
if ($2 != lastId) {
lastId = $2;
if (uniqueIds % 50 == 0) {
close("part"maxPart".csv");
maxPart++;
}
uniqueIds++;
}
print >> "part"maxPart".csv";
}' sorted.csv
要测试脚本,我使用生成了示例数据
n=98""000""000; paste -d,
<(shuf -i 10""000-99""000 -r -n "$n") \
<(shuf -i 0-9""999 -r -n "$n") \
| cat <(echo data,id) - > input.csv
样本数据有两列和9800万行,其中有数字。那里有1万个唯一ID。测量时间在哪里
tail -n +2 input.csv | LC_ALL=C sort -t, -k2 > sorted.csv
对输入进行排序。 tail
部分删除标题。结论:即使您的输入未排序,排序然后运行第二个脚本的速度也会更快。
答案 1 :(得分:0)
您应该对文件进行排序以获得最佳性能
$ sort -t, -k2,2 file | awk '!($2 in a){c++; a[$1];
if(c==50) {ix++; c=0}}
{print > "file_"(ix+1)}'
计算唯一键,并在50后递增文件计数器;将每一行打印到索引文件中。
如果仍然超出系统阈值,您可能仍需要关闭文件。