我有以下脚本,我在解析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
答案 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;
(一个好的manpage总是列出问题!)
sum
命令的16位校验和可能会发生冲突。答案 2 :(得分:0)
虽然一个合适的数据库引擎会为此做一个更好的工具,但仍然可以用awk做到这一点。
诀窍是排序您的数据,以便将具有相同名称的记录组合在一起。然后从上到下的单次传球足以找到比赛。这可以在线性时间内完成。
详细说明:
确保每一行都以名称开头。还要添加一个数字(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
中出现多次,则忽略除第一个之外的所有名称。