【发布时间】:2019-04-30 17:33:46
【问题描述】:
我有一个非常大的 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 初学者,所以我很确定我的代码效率不高。无论如何我可以更快地完成整个过程吗?
【问题讨论】:
-
awk 对于 bash 比这更有效,因为它缓存文件描述符。 (嗯,GNU 可以;其他实现可能要慢得多)。
-
并不是说你不能让 bash 的执行速度至少比你当前的 bash 方法快一个数量级(尽管它仍然永远不会像写得好的 awk 一样快)——诀窍是停止在每一行上使用
>>"$file_name",而是维护一个打开文件描述符的缓存。在 bash 4.1 中,如果您运行declare -A fds=( )创建关联数组,则打开一个新的 FD 并存储其 FD 编号看起来像exec {fd_num}>"$file_name";然后是fds[$file_name]=$fd_num——当然,只有在fds数组中没有file_name条目时才运行该代码。 -
类似地,在编写性能很重要的 shell 脚本时,不要使用内部循环等外部命令。对
cat或rm的任何调用都是fork()关闭子进程和exec从外部到外壳的软件。