根据列值将大文件拆分为较小文件的有效方法

时间:2018-11-28 18:18:55

标签: bash awk

我有一个非常大的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初学者,所以我很确定我的代码效率不高。无论如何,我可以更快地完成整个过程吗?

2 个答案:

答案 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。测量时间在哪里

  • 3分54秒,以在未排序的输入上运行第一个脚本。
  • 1m 19s使用tail -n +2 input.csv | LC_ALL=C sort -t, -k2 > sorted.csv对输入进行排序。 tail部分删除标题。
  • 1分48秒在已排序的输入上运行第二个脚本。
  • 3m 07s 用于排序和运行第二个脚本。

结论:即使您的输入未排序,排序然后运行第二个脚本的速度也会更快。

答案 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后递增文件计数器;将每一行打印到索引文件中。

如果仍然超出系统阈值,您可能仍需要关闭文件。