【问题标题】:Remove duplicate entries in file - Optimise performance删除文件中的重复条目 - 优化性能
【发布时间】:2022-01-22 13:32:31
【问题描述】:

我每天需要扫描数十万个文件以从中删除重复条目。这些文件中的每一个都有数千条记录

示例输入文件

2019-10-04,3.9,3.29,5.85,6.15
2019-10-05,3.8,7.02,5.69,6.83
2019-10-05,3.8,8.02,8.69,1.83
2019-10-07,1.8,1.02,4.69,7.83

这是我为它编写的脚本,大约需要一个小时或更长时间才能完成。

脚本

#!/bin/bash

LOOKUP_DIR="/path/to/source_files"
CLEANEDUP_DIR="/path/to/cleaned_content"


remove_dup(){
    fname=${1}
    awk -F"," 'prev && ($1 != prev) {print seen[prev]} {seen[$1] = $0; prev = $1} END {print seen[$1]}' "${fname}" > "${CLEANEDUP_DIR}/${fname}"
}

cd ${LOOKUP_DIR}
for k in *.csv
do 
    remove_dup "${k}" &
done

wait

检查重复项的方法是查看第一个字段,如果该字段(在本例中为日期)有多个条目,则只需要保留包含此日期的最后一行,其余的则删除。

请问有没有办法优化我写的逻辑?

【问题讨论】:

  • 行是按第一个字段排序的吗?
  • 文件是否有可能按照2019-10-05,...2019-10-07,...2019-10-05,...的顺序记录?
  • 当您决定是否重复时,是否应该只包含第 1 列?如果是,它选择带有2019-10-05 的两行中的哪一行有关系吗?
  • @M.NejatAydin - 是的,您提到的订单可以包含在 2019-10-05、...、2019-10-07、...、 2019-10-05,...
  • 如果您有数十万个文件,那么您几乎无法同时处理它们,除非您有足够的 CPU 和 IO 来备份它。根据您的硬件配置,您可能希望使用 GNU 并行来确定要生成的正​​确进程数。

标签: bash shell


【解决方案1】:

如果我理解您的问题并且您想从每个文件中删除任何重复的记录,则使用 awk 中的一对数组,第一个使用计数器作为索引,以便保持记录顺序存储连接的 5 个字段SUBSEP 存储的值。由SUBSEP 连接的5 个字段索引的第二个数组将记录保存为存储值。这允许在使用 index in array 测试之前简单检查这 5 个字段是否已被看到。

与其在remove_dup() 中编写脚本,不如编写一个从remove_dup() 调用的可执行awk 脚本。脚本可以是:

#!/usr/bin/awk -f

BEGIN { FS="," }

{ if ($1 SUBSEP $2 SUBSEP $3 SUBSEP $4 SUBSEP $5 in array)
    next
  order[++n] = $1 SUBSEP $2 SUBSEP $3 SUBSEP $4 SUBSEP $5
  array[$1,$2,$3,$4,$5] = $0
}

END {
  for (i=1; i<=n; i++)
    print array[order[i]]
}

(仅当连接字段不作为array 中的索引存在时才存储以上记录,确保删除所有重复项,保持第一次出现的顺序并丢弃所有其他项)

然后你可以修改你的脚本为:

#!/bin/bash

LOOKUP_DIR="/path/to/source_files"
CLEANEDUP_DIR="/path/to/cleaned_content"
AWKSCRIPT="/path/to/executable/awkscript"

remove_dup(){
    fname=${1}
    $AWKSCRIPT "${fname}" > "${CLEANEDUP_DIR}/${fname}"
}

cd ${LOOKUP_DIR}
for k in *.csv
do 
    remove_dup "${k}" &
done

wait

(注意添加了存储在变量AWKSCRIPT中的可执行文件awkscript的路径)

这应该做你所追求的。

【讨论】:

  • 非常感谢大卫。我会试一试并报告
  • 你没有提到瓶颈的一个可能原因是产生了几十万个同时读取和写入磁盘的进程。
  • 谢谢大卫。这就像上面的排序命令一样完成工作。您的解决方案和来自 Kamil 的解决方案需要完全相同的时间来完成。
  • 很高兴它有帮助。表明使用awk给猫换皮的方法总是不止一种。
【解决方案2】:

试试:

tac thefile | sort -urst, -k1,1

优化性能

用一种编程语言重写它。不要使用进程 - 为每个文件使用线程。对于脚本,请使用 Python 或 Ruby。对于编译,请使用 C++ 或 C。编写过程只需不到一个小时,而且很可能比 fork()为每个文件创建一个新进程要快得多:

#include <map>
#include <future>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <filesystem>
#include <ostream>
#include <vector>

std::string algo(const std::filesystem::path& file) {
    std::map<std::string, std::string> lines;
    std::ifstream ffile(file);
    std::string line;
    std::string field;
    size_t pos;
    while (std::getline(ffile, line)) {
        pos = 0;
        for (auto&& c : line) {
            if (c == ',') {
                break;
            }
            pos++;
        }
        field = line.substr(0, pos);
        lines.insert_or_assign(std::move(field), std::move(line));
    }
    std::ostringstream of;
    for (auto&& i : lines) {
        of << i.second << '\n';
    }
    return of.str();
}

int main(int argc, char *argv[]) {
    const std::filesystem::path p(argv[1]);
    std::vector<
        std::pair<
            std::string, std::future<std::string>
            >
        > results;
    if (std::filesystem::is_regular_file(p)) {
        std::cout << algo(p) << '\n';
    } else {
        for (auto&& f : std::filesystem::directory_iterator(p)) {
            if (f.is_regular_file()) {
                results.emplace_back(f.path(), std::async(algo, f.path()));
            }
        }
    }
    for (auto&& r : results) {
        std::cout
            << "=== " << r.first << " ===\n\n"
            << r.second.get() << '\n';
    }
}

【讨论】:

  • 您好 - 非常感谢。关于 tac 解决方案,运行 tac 的目的是什么以及使用 'r' 反向排序的目的是什么?
  • tac 使用稳定排序应该保留最后一个条目。 sort -su -k1.1 将保留第一个。好吧,r 是这样的,所以行的顺序就像输入一样,它在那里没有做任何特别的事情。
  • 删除'r'选项会导致错误的结果吗?我问,因为内容最后是反向排序的。我希望最后的最新记录由于使用 -r 选项而成为第一条记录
  • 不,没有效果。好吧,试试吧;)
  • 酷谢谢。这看起来很有希望且易于理解。立即试用
猜你喜欢
  • 2012-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-06
  • 2015-03-12
  • 2023-04-06
  • 1970-01-01
  • 2016-10-27
相关资源
最近更新 更多