【问题标题】:Search large CSV files with multiple search criteria on unix在 unix 上使用多个搜索条件搜索大型 CSV 文件
【发布时间】:2011-10-11 21:31:39
【问题描述】:

我有几个大的 CSV 文件,我需要使用 1 到多个参数进行搜索,如果我找到一个匹配项,我需要将该行保存在另一个文件中。下面是一个成功运行但对 5gb 文件非常慢的 perl 代码示例。任何关于加快速度的建议将不胜感激。

#!/usr/bin/env perl
use Text::CSV_XS;

$numArgs = $#ARGV;

#First Parameter is the input file name
$Finput = $ARGV[0];
chomp($Finput);

#Second Parameter is the output file name
$Foutput = $ARGV[1];
chomp($Foutput);

# Open the Control file but quit if it doesn't exist
open(INPUT1, $Finput) or die "The Input File $Finput could not be found.\n";
open(OUTPUT1, ">$Foutput") or die "Cannot open output $Foutout file.\n";


my $csv = Text::CSV_XS->new();
open my $FH, "<", $Finput;

while (<$FH>) {
    $csv->parse($_);
    my @fields = $csv->fields;

    if ($fields[0] == 10000) {
        if ($fields[34] eq 'abcdef') {
            if ($fields[103] == 9999) {
                print OUTPUT1 "$_\n";
            }
        }
    }
}

【问题讨论】:

  • if( $fields[0] = 10000)... 和 if( $fields[34] = 'abcdef' ) 可能没有按照你的想法做。您可能需要== 运算符(用于数字比较)和eq 运算符用于字符串比较。很难想象这段代码实际上按照发布的方式正确运行。
  • 您也永远不会在示例代码中阅读 INPUT1。为了获得真正准确的答案,我们需要知道您现有的(工作)算法是什么样的,或者您的输入和输出数据应该是什么样的。由于发布的代码不能准确表示您实际成功但运行缓慢,因此我们只能猜测您真正需要什么。
  • 感谢 DavidO...我更正了运算符,并且在调用此脚本时确实接受了 2 个参数,所以我像这样运行它...perl script.pl
  • 继续思考...所以我像这样运行它...perl script.pl input_file.csv out_putfile.csv...这确实读入第一个文件并循环通过它并产生第二个如果找到文件。输入日期是这样的..(20110718043719,10000,"NAME, Association",1110101,,I,1,1,USA,USA......新行然后是另一行 20110718043719,10000,"NAME, Association" ,1110101,,I,1,1,USA,USA)。如果找到匹配项,我们应该将整行复制到新文件中。

标签: perl file search csv


【解决方案1】:

我不知道你的数据或你的标准。

但如果我们可以使用上面给出的示例,那么我会在处理 CSV 之前尝试对这些行进行简单的测试。

例如(注意,我的 perl 很糟糕,这是为了示范,不正确):

if (/.*10000.*abcdef.*9999.*/) {
    $csv->parse($_);
    if ($fields[0] = 10000) {
        ...
    }
}

基本上,您可以先进行一些更简单、更快速的检查,以更快地取消行的资格,然后再执行必要的额外处理来使它们合格。

很明显,如果匹配的行多于不匹配的行,或者简单限定条件的检查并不实用,那么这种技术将不起作用。

做对了,CSV 解析有点贵(事实上,假设单行 CSV 是单条记录,您在此处遇到错误,这可能适用于您的数据,但 CSV 实际上允许嵌入换行符,所以它不是可以对所有 CSV 进行的通用假设)。

所以,如果“一目了然”,该行无论如何都不匹配,则不必为解析它付出代价。

【讨论】:

  • 我明白你在解析前预验证的观点,让我试一试,看看是否能节省一些时间。
  • 我怀疑在包含数百个字段的行上执行正则表达式搜索会比 if 中的内容更快。
  • 综合起来,整体的过程可能每行会慢一些,但基本前提是假行比真行多,所以总体上比较快。没有什么说您必须检查所有字段,对 10000 的简单测试可能足以拒绝大多数行。 perl 中的正则表达式是用 C 编写的,速度非常快,我不知道是 CSV 模块是用什么编写的,但它很可能是在 Perl 本身中编写的。 CSV 模块必然会创建大量数据(例如字段),如果实际上该行被拒绝,则不需要这些数据。
  • 这是一个很好的建议如果不可能在字段中嵌入\n。但是将正则表达式分解为三个较小的匹配会更有效。示例:if( /\D10000\D/ and /\babcdef\b/ and /\D9999\D/ ) { $csv-&gt;parse($_); ....。目标是三个易于匹配的小型正则表达式,它们永远不会有任何回溯复杂性。如果正则表达式不检查字段顺序,这真的没关系。目标只是拒绝大部分“不可能”的行,而对一小部分行进行额外检查。
【解决方案2】:

这是“成功”运行的代码吗?我觉得这很难相信。

if ($fields[0] = 10000) {
    if ($fields[34] = 'abcdef') {
        if ($fields[103] = 9999) {

这些不是相等性检查,而是赋值。所有这些 if 子句将始终返回 true。你可能想要的是==eq,而不是=

您还在输入文件上打开了两个文件句柄,并以错误的方式使用了 CSV 模块。我不相信这些小错误会导致脚本太慢,但它会打印那个 5gb 文件中的所有记录。

这是您的脚本的修订版本。

use strict;
use warnings;
use Text::CSV;
use autodie;

my $Finput = $ARGV[0];
my $Foutput = $ARGV[1];

open my $FH, "<", $Finput;
open my $out, ">", $Foutput;

my $csv = Text::CSV->new();

while (my $row = $csv->getline($FH)) {
    my @fields = @$row;
    if ($fields[0] == 10000) {
        if ($fields[34] eq 'abcdef') {
            if ($fields[103] == 9999) {
                $csv->print($out, $row);
            }
        }
    }
}

autodie pragma 将为我们(和其他事情)检查来自open 的返回值。 use strict; use warnings; 会让我们的大脑受到的伤害更少。哦,我使用的是Text::CSV,而不是_XS 版本。

【讨论】:

  • 感谢 TLP,我在输入消息时添加了嵌入的 if 语句,所以是的,你是对的,它们是错误的。输入/输出读取/写入在我的代码下正常工作,而不是我在这里真正担心的事情。谁能看到加快大文件搜索速度的解决方案?我看不出这个答案是如何解决这个问题的,也许我错过了?
  • @Nemo,这个答案只是将原件重写得更干净、更正确。它根本没有解决性能问题(这是问题,而不是“这是一个写得很好的 Perl 程序”)。海报甚至承认这一点。其他答案实际上试图通过我的正则表达式测试或 pre-grep 结果来解决性能问题。这些答案是否适用或成功取决于数据集和其他因素。
  • @jpm 使用您当前的数据,我相信这与 perl 一样快。正如 Will 在下面指出的,您可以尝试先进行快速检查以丢弃不需要的行,但这不太可能大大提高速度。如果性能是一个问题,您很可能会从转向另一种数据存储形式(如 SQL)中受益。
  • 这个答案可以通过 (1) 使用 Text::CSV_XS 和 (2) 不复制 while 循环中的每一行来改进。无需使用my @fields = @$row 创建副本。那是额外的工作。只需对$row-&gt;[0]$row-&gt;[34] 等进行操作即可。解引用比复制数组便宜很多。
【解决方案3】:

您想对每个文件使用 grep "{searchstring}" filename1.csv filename2.csv > savefile.txt。也许您想逐行阅读 filename.csv:

#!/bin/bash
exec 3<filename.csv
while read haystack <&3
do
  grep "{needle}" $haystack > result.txt 
done

【讨论】:

  • grep 或 egrep 可以工作,但有没有办法构建这些命令并确保当它在 csv 行的第 103 位而不是其他地方(如 50)找到 9999 时我会受到打击?
猜你喜欢
  • 2018-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-15
相关资源
最近更新 更多