Bash脚本处理速度太慢

时间:2015-03-26 12:17:12

标签: bash

我有以下脚本,我在解析2个csv文件以查找MATCH,每个文件有10000行。但是处理需要很长时间!这是正常的吗?

我的剧本:

#!/bin/bash 

IFS=$'\n'

CSV_FILE1=$1;
CSV_FILE2=$2;

sort -t';' $CSV_FILE1 >> Sorted_CSV1
sort -t';' $CSV_FILE2 >> Sorted_CSV2

echo "PATH1 ; NAME1 ; SIZE1 ; CKSUM1 ; PATH2 ; NAME2 ; SIZE2 ; CKSUM2"  >> 'mapping.csv'


while read lineCSV1 #Parse 1st CSV file
do

       PATH1=`echo $lineCSV1 | awk '{print $1}'`
       NAME1=`echo $lineCSV1 | awk '{print $3}'`
       SIZE1=`echo $lineCSV1 | awk '{print $7}'`
       CKSUM1=`echo $lineCSV1 | awk '{print $9}'`      

    while read lineCSV2   #Parse 2nd CSV file
    do
       PATH2=`echo $lineCSV2 | awk '{print $1}'`
       NAME2=`echo $lineCSV2 | awk '{print $3}'`
       SIZE2=`echo $lineCSV2 | awk '{print $7}'`
       CKSUM2=`echo $lineCSV2 | awk '{print $9}'`

       # Test if NAM1 MATCHS NAME2

        if [[ $NAME1 == $NAME2 ]]; then

            #Test checksum OF THE MATCHING NAME

                if [[ $CKSUM1 != $CKSUM2 ]]; then                   

            #MAPPING OF THE MATCHING LINES  
                echo $PATH1 ';' $NAME1 ';' $SIZE1 ';' $CKSUM1 ';' $PATH2 ';' $NAME2 ';' $SIZE2 ';' $CKSUM2 >> 'mapping.csv'
                fi
        break #When its a match break the while loop and go the the next Row of the 1st CSV File
        fi       
    done < Sorted_CSV2 #Done CSV2


done < Sorted_CSV1 #Done CSV1

3 个答案:

答案 0 :(得分:1)

这是二次序。另外,请参阅Tom Fenech评论:您在另一个循环内的循环内多次调用awk。不要将awk用于每行中的字段,而是尝试将IFS shell变量设置为&#34 ;;&#34;并直接在read命令中读取字段:

IFS=";"
while read FIELD11 FIELD12 FIELD13; do
    while read FIELD21 FIELD22 FIELD23; do
        ...
    done <Sorted_CSV2
done <Sorted_CSV1

虽然,这仍然是O(N ^ 2)并且非常低效。您似乎通过重合字段匹配2个字段。使用join command line utility可以更轻松,更快地完成此任务,并将从O(N ^ 2)到O(N)的顺序降低。

答案 1 :(得分:0)

每当你说“这个文件/数据列表/表是否有与此文件/数据列表/表匹配的东西?”时,你应该想到关联数组(有时称为哈希< / em>的)。

关联数组按特定值键入,每个键与一个值相关联。好处是找到一把钥匙非常快。

在循环循环中,每个文件中有10,000行。你的外循环执行了10,000次。您的内部循环可能会对第一个文件中的每一行执行10,000次。那是你经历内循环的10,000 x 10,000倍。这可能通过内循环循环1亿次。认为你可以看到为什么你的程序可能有点慢

在这个时代,拥有10,000个成员关联数组并不是那么糟糕。 (想象一下,在1980年,在具有256K的MS-DOS系统上执行此操作。它不起作用)。那么,让我们浏览第一个文件,创建一个10,000个成员关联数组,然后浏览第二个文件,寻找匹配的行。

Bash 4.x有关联数组,但我的系统上只有Bash 3.2,所以我无法在Bash中给你答案。

此外,有时候Bash不是特定问题的答案。 Bash可能有点慢,语法可能容易出错。 awk可能更快,但许多版本没有关联数组。对于像Python或Perl这样的更高级脚本语言来说,这确实是一项工作。

由于我不能做Bash回答,这是Perl的回答。也许这会有所帮助。或者,这可能会激发Bash 4.x可以在Bash中给出答案的人。

我基本上打开第一个文件并创建一个由校验和键控的关联数组。如果这是一个sha1校验和,它对于所有文件应该是唯一的(除非它们完全匹配)。如果你没有sha1校验和,你需要按摩一下这个结构,但这几乎是一样的想法。

一旦我找到关联数组,然后我打开文件#2,只是查看文件中是否已存在校验和。如果是的话,我知道我有一个匹配的行,并打印出两个匹配。

我必须在第一个文件中循环10,000次,在第二个文件中循环10,000次。这只有20,000个循环,而不是1000万个,循环次数减少了20,000倍,这意味着程序的运行速度要快20,000倍。因此,如果程序使用双循环运行需要2整天,那么关联数组解决方案将在不到一秒的时间内完成。

#! /usr/bin/env perl
#
use strict;
use warnings;
use autodie;

use feature qw(say);

use constant {
    FILE1       => "file1.txt",
    FILE2       => "file2.txt",
    MATCHING    => "csv_matches.txt",
};

#
# Open the first file and create the associative array
#
my %file_data;

open my $fh1, "<", FILE1;

while ( my $line = <$fh1> ) {
    chomp $line;
    my ( $path, $blah, $name, $bather, $yadda, $tl_dr, $size, $etc, $check_sum ) = split /\s+/, $line;
    #
    # The main key is "check_sum" which **should** be unique, especially if it's a sha1
    #
    $file_data{$check_sum}->{PATH} = $path;
    $file_data{$check_sum}->{NAME} = $name;
    $file_data{$check_sum}->{SIZE} = $size;
}
close $fh1;

#
# Now, we have the associative array keyed by the data we want to match, read file 2
#

open my $fh2, "<", FILE2;
open my $csv_fh, ">", MATCHING;
while ( my $line = <$fh2> ) {
    chomp $line;
    my ( $path, $blah, $name, $bather, $yadda, $tl_dr, $size, $etc, $check_sum ) = split /\s+/, $line;
    #
    # If there is a matching checksum in file1, we know we have a matching entry
    #
    if ( exists $file_data{$check_sum} ) {
        printf {$csv_fh} "%s;%s:%s:%s:%s:%s\n",
           $file_data{$check_sum}->{PATH}, $file_data{$check_sum}->{NAME}, $file_data{$check_sum}->{SIZE},
           $path, $name, $size;
   }
}
close $fh2;
close $csv_fh;

BUGS

(一个好的manpage总是列出问题!)

  • 这假设每个文件匹配一个。如果file1或file2中有多个重复项,则只会选择最后一个重复项。
  • 这假定为sha256或等效的校验和。在这样的校验和中,除非匹配,否则两个文件极不可能具有相同的校验和。来自历史sum命令的16位校验和可能会发生冲突。

答案 2 :(得分:0)

虽然一个合适的数据库引擎会为此做一个更好的工具,但仍然可以用awk做到这一点。

诀窍是排序您的数据,以便将具有相同名称的记录组合在一起。然后从上到下的单次传球足以找到比赛。这可以在线性时间内完成。

详细说明:

在两个CSV文件中插入两列

确保每一行都以名称开头。还要添加一个数字(1或2),表示该行来自哪个文件。当我们将两个文件合并在一起时,我们将需要这个。

awk -F';' '{ print $2 ";1;" $0 }' csvfile1 > tmpfile1
awk -F';' '{ print $2 ";2;" $0 }' csvfile2 > tmpfile2

连接文件,然后对行

进行排序
sort tmpfile1 tmpfile2 > tmpfile3

扫描结果,报告不匹配

awk -F';' -f scan.awk tmpfile3

scan.awk包含的位置:

BEGIN {
    origin = 3;
}

$1 == name && $2 > origin && $6 != checksum {
    print record;
}

{
    name = $1;
    origin = $2;
    checksum = $6;
    sub(/^[^;]*;.;/, "");
    record = $0;
}

全部放在一起

一起塞进Bash oneliner,没有明确的临时文件:

(awk -F';' '{print $2";1;"$0}' csvfile1 ; awk -F';' '{print $2";2;"$0}' csvfile2) | sort | awk -F';' 'BEGIN{origin=3}$1==name&&$2>origin&&$6!=checksum{print record}{name=$1;origin=$2;checksum=$6;sub(/^[^;]*;.;/,"");record=$0;}'

注意:

  • 如果同一名称在csvfile1中出现多次,则忽略除最后一个之外的所有名称。
  • 如果同一名称在csvfile2中出现多次,则忽略除第一个之外的所有名称。