web-dev-qa-db-fra.com

Le moyen le plus rapide de trouver des lignes d'un fichier à partir d'un autre fichier plus volumineux dans Bash

J'ai deux fichiers, file1.txt et file2.txt. file1.txt compte environ 14 000 lignes et file2.txt a environ 2 milliards. file1.txt a un seul champ f1 par ligne pendant que file2.txt a 3 champs, f1 à travers f3, délimité par |.

Je veux trouver toutes les lignes de file2.txtf1 de file1.txt allumettes f2 de file2.txt (ou n'importe où sur la ligne si nous ne voulons pas passer plus de temps à diviser les valeurs de file2.txt).

file1.txt (environ 14K lignes, non trié):

foo1
foo2
...
bar1
bar2
...

file2.txt (environ 2 milliards de lignes, non triées):

date1|foo1|number1
date2|foo2|number2
...
date1|bar1|number1
date2|bar2|number2
...

Sortie attendue:

date1|foo1|number1
date2|foo2|number2
...
date1|bar1|number1
date2|bar2|number2
...

Voici ce que j'ai essayé et cela semble prendre plusieurs heures à courir:

fgrep -F -f file1.txt file2.txt > file.matched

Je me demande s'il existe un moyen meilleur et plus rapide de faire cette opération avec les commandes Unix courantes ou avec un petit script.

23
codeforester

Un petit morceau de code Perl a résolu le problème. Voici l'approche adoptée:

  • stocker les lignes de file1.txt dans un hachage
  • lis file2.txt ligne par ligne, analyser et extraire le deuxième champ
  • vérifier si le champ extrait est dans le hachage; si oui, imprimez la ligne

Voici le code:

#!/usr/bin/Perl -w

use strict;
if (scalar(@ARGV) != 2) {
  printf STDERR "Usage: fgrep.pl smallfile bigfile\n";
  exit(2);
}

my ($small_file, $big_file) = ($ARGV[0], $ARGV[1]);
my ($small_fp, $big_fp, %small_hash, $field);

open($small_fp, "<", $small_file) || die "Can't open $small_file: " . $!;
open($big_fp, "<", $big_file)     || die "Can't open $big_file: "   . $!;

# store contents of small file in a hash
while (<$small_fp>) {
  chomp;
  $small_hash{$_} = undef;
}
close($small_fp);

# loop through big file and find matches
while (<$big_fp>) {
  # no need for chomp
  $field = (split(/\|/, $_))[1];
  if (defined($field) && exists($small_hash{$field})) {
    printf("%s", $_);
  }
}

close($big_fp);
exit(0);

J'ai exécuté le script ci-dessus avec 14K lignes dans file1.txt et 1,3M lignes dans file2.txt. Il s'est terminé en 13 secondes environ, produisant 126 000 matchs. Voici la sortie time pour la même chose:

real    0m11.694s
user    0m11.507s
sys 0m0.174s

J'ai exécuté le code awk de @ Inian:

awk 'FNR==NR{hash[$1]; next}{for (i in hash) if (match($0,i)) {print; break}}' file1.txt FS='|' file2.txt

C'était beaucoup plus lent que la solution Perl, car elle boucle 14K fois pour chaque ligne dans file2.txt - ce qui est vraiment cher. Il a avorté après avoir traité 592K enregistrements de file2.txt et produisant 40K lignes appariées. Voici combien de temps cela a pris:

awk: illegal primary in regular expression 24/Nov/2016||592989 at 592989
 input record number 675280, file file2.txt
 source line number 1

real    55m5.539s
user    54m53.080s
sys 0m5.095s

Utilisation de l'autre solution awk de @ Inian, ce qui élimine le problème de boucle:

time awk -F '|' 'FNR==NR{hash[$1]; next}$2 in hash' file1.txt FS='|' file2.txt > awk1.out

real    0m39.966s
user    0m37.916s
sys 0m0.743s

time LC_ALL=C awk -F '|' 'FNR==NR{hash[$1]; next}$2 in hash' file1.txt FS='|' file2.txt > awk.out

real    0m41.057s
user    0m38.475s
sys 0m0.904s

awk est très impressionnant ici, étant donné que nous n'avons pas eu à écrire un programme entier pour le faire.

J'ai exécuté @=iv Python également. Il a fallu environ 15 heures pour terminer le travail, et il semblait qu'il produisait les bons résultats. Construire une énorme expression régulière n'est pas aussi efficace que d'utiliser un hachage Voici la sortie time:

real    895m14.862s
user    806m59.219s
sys 1m12.147s

J'ai essayé de suivre la suggestion d'utiliser parallèle . Cependant, il a échoué avec fgrep: memory exhausted erreur, même avec de très petites tailles de bloc.


Ce qui m'a surpris, c'est que fgrep était totalement inadapté à cela. Je l'ai avorté après 22 heures et il a produit environ 100 000 matchs. J'aimerais que fgrep ait une option pour forcer le contenu de -f file à conserver dans un hachage, tout comme ce que le code Perl a fait.

Je n'ai pas vérifié l'approche join - je ne voulais pas la surcharge supplémentaire de tri des fichiers. De plus, étant donné les mauvaises performances de fgrep, je ne pense pas que join aurait fait mieux que le code Perl.

Merci à tous pour votre attention et vos réponses.

5
codeforester

Une solution Perl. [Voir Remarque ci-dessous.]

Utilisez un hachage pour le premier fichier. Lorsque vous lisez le gros fichier ligne par ligne, extrayez le champ par expression régulière (capture le premier motif entre ||) Ou split (obtient le deuxième mot) et imprimez-le exists. Ils diffèrent probablement un peu en vitesse (chronométrez-les). La vérification defined n'est pas nécessaire dans l'expression régulière tandis que pour split utilisez // (Défini ou) qui court-circuite.

use warnings;
use strict;

# If 'prog smallfile bigfile' is the preferred use
die "Usage: $0 smallfile bigfile\n"  if @ARGV != 2;
my ($smallfile, $bigfile) = @ARGV;

open my $fh, '<', $smallfile or die "Can't open $smallfile: $!";    
my %Word = map { chomp; $_ => 1 } <$fh>;

open    $fh, '<', $bigfile or die "Can't open $bigfile: $!";       
while (<$fh>) 
{
    exists $Word{ (/\|([^|]+)/)[0] } && print;  

    # Or
    #exists $Word{ (split /\|/)[1] // '' } && print;
}
close $fh;

Éviter la branche if et utiliser les courts-circuits est plus rapide, mais très peu. Sur des milliards de lignes, ces ajustements s'additionnent, mais encore une fois pas trop. Il peut (ou non) être un peu plus rapide pour lire le petit fichier ligne par ligne, au lieu du contexte de liste comme ci-dessus, mais cela devrait pas être perceptible.

Mise à jour L'écriture dans STDOUT enregistre deux opérations et je la chronomètre à plusieurs reprises pour qu'elle soit un peu plus rapide que l'écriture dans un fichier. Une telle utilisation est également compatible avec la plupart des outils UNIX, j'ai donc changé pour écrire dans STDOUT. Ensuite, le test exists n'est pas nécessaire et le supprimer épargne une opération. Cependant, j'obtiens toujours un meilleur temps d'exécution avec ça, alors qu'il transmet également mieux le but. Au total, je le laisse. Merci à ikegami pour les commentaires.

Remarque La version commentée est environ 50% plus rapide que l'autre, par mon repère ci-dessous. Celles-ci sont toutes deux données car elles sont différentes, l'une trouvant la première correspondance et l'autre le deuxième champ. Je le garde comme un choix plus générique, car la question est ambiguë à ce sujet.


Quelques comparaisons (benchmark) [Mis à jour pour écrire dans STDOUT, voir "Mettre à jour" ci-dessus]

Il y a une analyse approfondie dans le réponse de HåkonHægland , chronométrant un cycle de la plupart des solutions. Voici une autre prise, comparant les deux solutions ci-dessus, la propre réponse de l'OP et la fgrep publiée, qui devrait être rapide et utilisée dans la question et dans de nombreuses réponses.

Je crée des données de test de la manière suivante. Une poignée de lignes de la longueur à peu près comme indiqué sont faites avec des mots aléatoires, pour les deux fichiers, afin de correspondre dans le deuxième champ. Ensuite, je remplis cette "graine" pour les échantillons de données avec des lignes qui ne correspondent pas, donc pour imiter les rapports entre les tailles et les correspondances citées par OP: pour 14K lignes dans un petit fichier, il y a 1,3M lignes dans le gros fichier, ce qui donne 126K correspondances. Ensuite, ces exemples sont écrits à plusieurs reprises pour créer des fichiers de données complets sous forme d'OP, shuffle- édités à chaque fois en utilisant List :: Util .

Toutes les exécutions comparées ci-dessous produisent des correspondances de 106_120 Pour les tailles de fichier ci-dessus (diff- ed pour une vérification), donc la fréquence de correspondance est assez proche. Ils sont comparés en appelant des programmes complets à l'aide de my $res = timethese(60 ...). Le résultat de cmpthese($res) sur v5.16 est

 Taux regex c pour fgrep divisé 
 Regex 1,05/s - -23% -35% -44% 
 Cfor 1,36/s 30% - -16% -28% 
 divisé 1,62/s 54% 19% - -14% 
 fgrep 1,89/s 80% 39% 17% - 

Le fait que le programme C optimisé fgrep arrive en tête n'est pas surprenant. Le décalage de " regex" derrière " split" peut être dû à la surcharge de démarrage du moteur pour les petits matchs, beaucoup fois. Cela peut varier selon les versions de Perl, compte tenu des optimisations du moteur regex en constante évolution. J'inclus la réponse de @codeforester (" cfor") car elle a été déclarée la plus rapide, et son 20% Est en retard sur le très similaire " split "est probablement dû à de petites inefficacités dispersées (voir un commentaire sous cette réponse).

Ce n'est pas complètement différent, bien qu'il existe des variations sûres entre le matériel et les logiciels et les détails des données. J'ai exécuté ceci sur différentes Perls et machines, et la différence notable est que dans certains cas fgrep était en effet un ordre de grandeur plus rapide.

L'expérience de l'OP de très lent fgrep est surprenante. Compte tenu de leurs temps d'exécution cités, d'un ordre de grandeur plus lent que ci-dessus, je suppose qu'il y a un vieux système à "blâmer".

Même si cela est entièrement basé sur les E/S, il est avantageux de le placer sur plusieurs cœurs et je m'attendrais à une bonne accélération, jusqu'à un facteur de quelques-uns.


Hélas, le commentaire a été supprimé (?). En bref: utilisation inutile d'un scalaire (coûts), d'une branche if, de defined, de printf au lieu de print (lent!). Ceux-ci comptent pour l'efficacité sur 2 milliards de lignes.

18
zdim

J'ai essayé de faire une comparaison entre certaines des méthodes présentées ici.

J'ai d'abord créé un script Perl pour générer les fichiers d'entrée _file1.txt_ et _file2.txt_. Afin de comparer certaines des solutions, je me suis assuré que les mots de _file1.txt_ ne pouvaient apparaître que dans le deuxième champ de _file2.txt_. Aussi pour pouvoir utiliser la solution join présentée par @GeorgeVasiliou, j'ai trié _file1.txt_ et _file2.txt_. Actuellement, j'ai généré les fichiers d'entrée sur la base de seulement 75 mots aléatoires (extraits de https://www.randomlists.com/random-words ). Seulement 5 de ces 75 mots ont été utilisés dans _file1.txt_ les 70 mots restants ont été utilisés pour remplir les champs dans _file2.txt_. Il pourrait être nécessaire d'augmenter considérablement le nombre de mots pour obtenir des résultats réalistes (selon le PO, le _file1.txt_ original contenait 14000 mots). Dans les tests ci-dessous, j'ai utilisé un _file2.txt_ avec 1000000 (1 million) de lignes. Le script génère également le fichier _regexp1.txt_ requis par la solution grep de @BOC.

gen_input_files.pl :

_#! /usr/bin/env Perl
use feature qw(say);
use strict;
use warnings;

use Data::Printer;
use Getopt::Long;

GetOptions ("num_lines=i" => \my $nlines )
  or die("Error in command line arguments\n");

# Generated random words from site: https://www.randomlists.com/random-words
my $Word_filename        = 'words.txt'; # 75 random words
my $num_match_words      = 5;
my $num_file2_lines      = $nlines || 1_000_000;
my $file2_words_per_line = 3;
my $file2_match_field_no = 2;
my $file1_filename       = 'file1.txt';
my $file2_filename       = 'file2.txt';
my $file1_regex_fn       = 'regexp1.txt';

say "generating $num_file2_lines lines..";
my ( $words1, $words2 ) = get_words( $Word_filename, $num_match_words );

write_file1( $file1_filename, $words2 );
write_file2(
    $file2_filename, $words1, $words2, $num_file2_lines,
    $file2_words_per_line, $file2_match_field_no
);
write_BOC_regexp_file( $file1_regex_fn, $words2 );


sub write_BOC_regexp_file {
    my ( $fn, $words ) = @_;

    open( my $fh, '>', $fn ) or die "Could not open file '$fn': $!";
    print $fh '\\|' . (join "|", @$words) . '\\|';
    close $fh;
}

sub write_file2 {
    my ( $fn, $words1, $words2, $nlines, $words_per_line, $field_no ) = @_;

    my $nwords1 = scalar @$words1;
    my $nwords2 = scalar @$words2;
    my @lines;
    for (1..$nlines) {
        my @words_line;
        my $key;
        for (1..$words_per_line) {
            my $Word;
            if ( $_ != $field_no ) {
                my $index = int (Rand $nwords1);
                $Word = @{ $words1 }[$index];
            }
            else {
                my $index = int (Rand($nwords1 + $nwords2) );
                if ( $index < $nwords2 ) {
                    $Word = @{ $words2 }[$index];
                }
                else {
                    $Word =  @{ $words1 }[$index - $nwords2];
                }
                $key = $Word;
            }
            Push @words_line, $Word;
        }
        Push @lines, [$key, (join "|", @words_line)];
    }
    @lines = map { $_->[1] } sort { $a->[0] cmp $b->[0] } @lines; 
    open( my $fh, '>', $fn ) or die "Could not open file '$fn': $!";
    print $fh (join "\n", @lines);
    close $fh;
}

sub write_file1 {
    my ( $fn, $words ) = @_;

    open( my $fh, '>', $fn ) or die "Could not open file '$fn': $!";
    print $fh (join "\n", sort @$words);
    close $fh;
}

sub get_words {
    my ( $fn, $N ) = @_;

    open( my $fh, '<', $fn ) or die "Could not open file '$fn': $!";
    my @words = map {chomp $_; $_} <$fh>;
    close $fh;

    my @words1 = @words[$N..$#words];
    my @words2 = @words[0..($N - 1)];
    return ( \@words1, \@words2 );
}
_

Ensuite, j'ai créé un sous-dossier solutions avec tous les cas de test:

_$ tree solutions/
solutions/
├── BOC1
│   ├── out.txt
│   └── run.sh
├── BOC2
│   ├── out.txt
│   └── run.sh
├── codeforester
│   ├── out.txt
│   ├── run.pl
│   └── run.sh
[...]
_

Ici, les fichiers _out.txt_ sont la sortie des greps pour chaque solution. Les scripts _run.sh_ exécutent la solution pour le cas de test donné.

Notes sur les différentes solutions

  • _BOC1_ : Première solution présentée par @BOC

    _grep -E -f regexp1.txt file2.txt
    _
  • _BOC2_ : Deuxième solution proposée par @BOC:

    _LC_ALL=C grep -E -f regexp1.txt file2.txt
    _
  • codeforester: Solution Perl acceptée par @codeforester (voir source )

  • _codeforester_orig_ : Solution originale présentée par @codeforested:

    _fgrep -f file1.txt file2.txt
    _
  • dawg: Python utilisant le dictionnaire et la ligne de séparation proposée par @dawg (voir source )

  • _gregory1_ : solution utilisant Gnu Parallel proposée par @gregory

    _parallel -k --pipepart -a file2.txt --block "$block_size" fgrep -F -f file1.txt
    _

    Voir la note ci-dessous pour savoir comment choisir _$block_size_.

  • _hakon1_ : solution Perl fournie par @ HåkonHægland (voir source ). Cette solution nécessite la compilation de l'extension c lors de la première exécution du code. Il ne nécessite pas de recompilation lorsque _file1.txt_ ou _file2.txt_ change. Remarque: Le temps utilisé pour compiler l'extension c lors de l'exécution initiale n'est pas inclus dans les temps d'exécution présentés ci-dessous.

  • ikegami: Solution utilisant l'expression rationnelle assemblée et utilisant _grep -P_ comme indiqué par @ikegami. Remarque: l'expression rationnelle assemblée a été écrite dans un fichier distinct _regexp_ikegami.txt_, de sorte que l'exécution de la génération de l'expression rationnelle n'est pas incluse dans la comparaison ci-dessous. Voici le code utilisé:

    _regexp=$(< "regexp_ikegami.txt")
    grep -P "$regexp" file2.txt
    _
  • _inian1_ : Première solution par @Inian en utilisant match()

    _awk 'FNR==NR{
        hash[$1]; next
    }
    {
       for (i in hash) if (match($0,i)) {print; break}
    }' file1.txt FS='|' file2.txt
    _
  • _inian2_ : Deuxième solution par @Inian en utilisant index()

    _awk 'FNR==NR{
        hash[$1]; next
    }
    {
       for (i in hash) if (index($0,i)) {print; break}
    }' file1.txt FS='|' file2.txt
    _
  • _inian3_ : Troisième solution en vérifiant @Inian uniquement _$2_ champ:

    _awk 'FNR==NR{
        hash[$1]; next
    }
    $2 in hash' file1.txt FS='|' file2.txt
    _
  • _inian4_ : 4ème soultion par @Inian (essentiellement identique à _codeforester_orig_ avec _LC_ALL_):

    _LC_ALL=C fgrep -f file1.txt file2.txt
    _
  • _inian5_ : 5ème solution par @Inian (identique à _inian1_ mais avec _LC_ALL_):

    _LC_ALL=C awk 'FNR==NR{
        hash[$1]; next
    }
    {
       for (i in hash) if (match($0,i)) {print; break}
    }' file1.txt FS='|' file2.txt
    _
  • _inian6_ : Identique à _inian3_ mais avec _LC_ALL=C_. Merci à @GeorgeVasiliou pour sa suggestion.

  • jjoao: Code C généré par flex compilé comme proposé par @JJoao (voir source ). Remarque: La recompilation de l'exectuable doit être effectuée à chaque fois que _file1.txt_ change. Le temps utilisé pour compiler l'exécutable n'est pas inclus dans les temps d'exécution présentés ci-dessous.

  • oliv: Python fourni par @oliv (voir source )

  • Vasiliou: Utilisation de join comme suggéré par @GeorgeVasiliou:

    _join --nocheck-order -11 -22 -t'|' -o 2.1 2.2 2.3 file1.txt file2.txt
    _
  • _Vasiliou2_ : Identique à Vasiliou mais avec _LC_ALL=C_.

  • zdim: Utilisation du script Perl fourni par @zdim (voir source ). Remarque: Cela utilise la version de recherche d'expression régulière (au lieu de la solution de ligne fractionnée).

  • _zdim2_ : Identique à zdim sauf qu'il utilise la fonction split au lieu de la recherche d'expression régulière pour le dans _file2.txt_.

Remarques

  1. J'ai expérimenté un peu avec le parallèle Gnu (voir la solution _gregory1_ ci-dessus) pour déterminer la taille de bloc optimale pour mon CPU. J'ai 4 cœurs et, actuellement, il semble que le choix optimal consiste à diviser le fichier (_file2.txt_) en 4 morceaux de taille égale et à exécuter un seul travail sur chacun des 4 processeurs. D'autres tests pourraient être nécessaires ici. Donc, pour le premier cas de test où _file2.txt_ est 20M, je règle _$block_size_ à 5M (voir la solution _gregory1_ ci-dessus), tandis que pour le cas plus réaliste présenté ci-dessous où _file2.txt_ est 268M, un _$block_size_ de 67M a été utilisé.

  2. Les solutions _BOC1_, _BOC2_, _codeforester_orig_, _inian1_, _inian4_, _inian5_ et _gregory1_ ont toutes utilisé une correspondance lâche. Cela signifie que les mots de _file1.txt_ ne devaient pas correspondre exactement dans le champ # 2 de _file2.txt_. Un match n'importe où sur la ligne a été accepté. Étant donné que ce comportement a rendu plus difficile leur comparaison avec les autres méthodes, certaines méthodes modifiées ont également été introduites. Les deux premières méthodes appelées _BOC1B_ et _BOC2B_ ont utilisé un fichier _regexp1.txt_ modifié. Les lignes dans le _regexp1.txt_ d'origine sur le formulaire _\|foo1|foo2|...|fooN\|_ qui correspondraient aux mots à n'importe quelle limite de champ. Le fichier modifié, _regexp1b.txt_, a ancré la correspondance au champ # 2 exclusivement en utilisant le formulaire _^[^|]*\|foo1|foo2|...|fooN\|_ à la place.

    Ensuite, les autres méthodes modifiées _codeforester_origB_, _inian1B_, _inian4B_, _inian5B_ et _gregory1B_ ont utilisé un _file1.txt_ modifié. Au lieu d'un littéral mot par ligne, le fichier modifié _file1b.txt_ en utilisait un regex par ligne sur le formulaire:

    _ ^[^|]*\|Word1\|
     ^[^|]*\|Word2\|
     ^[^|]*\|Word3\|
     [...]
    _

    et en outre, _fgrep -f_ a été remplacé par _grep -E -f_ pour ces méthodes.

Exécution des tests

Voici le script utilisé pour exécuter tous les tests. Il utilise la commande Bash time pour enregistrer le temps passé pour chaque script. Notez que la commande time renvoie trois fois différents appels real, user et sys. J'ai d'abord utilisé user + sys, mais je me suis rendu compte que c'était incorrect lors de l'utilisation de la commande parallèle Gnu, donc l'heure indiquée ci-dessous est maintenant la partie real retournée par time. Voir cette question pour plus d'informations sur les différentes heures renvoyées par time.

Le premier test est exécuté avec _file1.txt_ contenant 5 lignes et _file2.txt_ contenant _1000000_ lignes. Voici les 52 premières lignes du script _run_all.pl_, le reste du script est disponible ici .

run_all.pl

_#! /usr/bin/env Perl

use feature qw(say);
use strict;
use warnings;

use Cwd;
use Getopt::Long;
use Data::Printer;
use FGB::Common;
use List::Util qw(max shuffle);
use Number::Bytes::Human qw(format_bytes);
use Sys::Info;

GetOptions (
    "verbose"       => \my $verbose,
    "check"         => \my $check,
    "single-case=s" => \my $case,
    "expected=i"    => \my $expected_no_lines,
) or die("Error in command line arguments\n");

my $test_dir    = 'solutions';
my $output_file = 'out.txt';
my $wc_expected = $expected_no_lines; # expected number of output lines

my $tests       = get_test_names( $test_dir, $case );

my $file2_size  = get_file2_size();
my $num_cpus    = Sys::Info->new()->device( CPU => () )->count;

chdir $test_dir;
my $cmd = 'run.sh';
my @times;
for my $case (@$tests) {
    my $savedir = getcwd();
    chdir $case;
    say "Running '$case'..";
    my $arg = get_cmd_args( $case, $file2_size, $num_cpus );
    my $output = `bash -c "{ time -p $cmd $arg; } 2>&1"`;
    my ($user, $sys, $real ) = get_run_times( $output );
    print_timings( $user, $sys, $real ) if $verbose;
    check_output_is_ok( $output_file, $wc_expected, $verbose, $check );
    print "\n" if $verbose;
    Push @times, $real;
    #Push @times, $user + $sys; # this is wrong when using Gnu parallel
    chdir $savedir;
}

say "Done.\n";

print_summary( $tests, \@times );
_

Résultats

Voici le résultat de l'exécution des tests:

_$  run_all.pl --verbose
Running 'inian3'..
..finished in 0.45 seconds ( user: 0.44, sys: 0.00 )
..no of output lines: 66711

Running 'inian2'..
..finished in 0.73 seconds ( user: 0.73, sys: 0.00 )
..no of output lines: 66711

Running 'Vasiliou'..
..finished in 0.09 seconds ( user: 0.08, sys: 0.00 )
..no of output lines: 66711

Running 'codeforester_orig'..
..finished in 0.05 seconds ( user: 0.05, sys: 0.00 )
..no of output lines: 66711

Running 'codeforester'..
..finished in 0.45 seconds ( user: 0.44, sys: 0.01 )
..no of output lines: 66711

[...]
_

Résumé

[Les résultats obtenus par @Vasiliou sont indiqués dans la colonne du milieu.]

_                               |Vasiliou
My Benchmark                   |Results  |   Details
-------------------------------|---------|----------------------
inian4             : 0.04s     |0.22s    | LC_ALL fgrep -f [loose] 
codeforester_orig  : 0.05s     |         | fgrep -f [loose]
Vasiliou2          : 0.06s     |0.16s    | [LC_ALL join [requires sorted files]]
BOC1               : 0.06s     |         | grep -E [loose] 
BOC2               : 0.07s     |15s      | LC_ALL grep -E [loose] 
BOC2B              : 0.07s     |         | LC_ALL grep -E [strict] 
inian4B            : 0.08s     |         | LC_ALL grep -E -f [strict] 
Vasiliou           : 0.08s     |0.23s    | [join [requires sorted files]] 
gregory1B          : 0.08s     |         | [parallel + grep -E -f [strict]] 
ikegami            : 0.1s      |         | grep -P 
gregory1           : 0.11s     |0.5s     | [parallel + fgrep -f [loose]] 
hakon1             : 0.14s     |         | [Perl + c]
BOC1B              : 0.14s     |         | grep -E [strict] 
jjoao              : 0.21s     |         | [compiled flex generated c code] 
inian6             : 0.26s     |0.7s     | [LC_ALL awk + split+dict] 
codeforester_origB : 0.28s     |         | grep -E -f [strict] 
dawg               : 0.35s     |         | [python + split+dict] 
inian3             : 0.44s     |1.1s     | [awk + split+dict] 
zdim2              : 0.4s      |         | [Perl + split+dict] 
codeforester       : 0.45s     |         | [Perl + split+dict] 
oliv               : 0.5s      |         | [python + compiled regex + re.search()] 
zdim               : 0.61s     |         | [Perl + regexp+dict] 
inian2             : 0.73s     |1.7s     | [awk + index($0,i)] 
inian5             : 18.12s    |         | [LC_ALL awk + match($0,i) [loose]] 
inian1             : 19.46s    |         | [awk + match($0,i) [loose]] 
inian5B            : 42.27s    |         | [LC_ALL awk + match($0,i) [strict]] 
inian1B            : 85.67s    |         | [awk + match($0,i) [strict]] 

Vasiliou Results : 2 X CPU Intel 2 Duo T6570 @ 2.10GHz - 2Gb RAM-Debian Testing 64bit- kernel 4.9.0.1 - no cpu freq scaling.
_

Un cas de test plus réaliste

J'ai ensuite créé un cas plus réaliste avec _file1.txt_ ayant 100 mots et _file2.txt_ ayant 10 millions de lignes (taille de fichier 268Mb). J'ai extrait 1000 mots aléatoires du dictionnaire à _/usr/share/dict/american-english_ en utilisant _shuf -n1000 /usr/share/dict/american-english > words.txt_ puis extrait 100 de ces mots dans _file1.txt_ puis j'ai construit _file2.txt_ de la même manière que celle décrite ci-dessus pour le premier test Cas. Notez que le fichier de dictionnaire était codé en UTF-8 et j'ai supprimé tous les caractères non ASCII du _words.txt_.

Ensuite, je lance le test sans les trois méthodes les plus lentes du cas précédent. C'est à dire. _inian1_, _inian2_ et _inian5_ ont été omis. Voici les nouveaux résultats:

_gregory1           : 0.86s     | [parallel + fgrep -f [loose]]
Vasiliou2          : 0.94s     | [LC_ALL join [requires sorted files]]
inian4B            : 1.12s     | LC_ALL grep -E -f [strict] 
BOC2B              : 1.13s     | LC_ALL grep -E [strict] 
BOC2               : 1.15s     | LC_ALL grep -E [loose] 
BOC1               : 1.18s     | grep -E [loose] 
ikegami            : 1.33s     | grep -P 
Vasiliou           : 1.37s     | [join [requires sorted files]]
hakon1             : 1.44s     | [Perl + c]
inian4             : 2.18s     | LC_ALL fgrep -f [loose] 
codeforester_orig  : 2.2s      | fgrep -f [loose] 
inian6             : 2.82s     | [LC_ALL awk + split+dict] 
jjoao              : 3.09s     | [compiled flex generated c code] 
dawg               : 3.54s     | [python + split+dict] 
zdim2              : 4.21s     | [Perl + split+dict]
codeforester       : 4.67s     | [Perl + split+dict] 
inian3             : 5.52s     | [awk + split+dict] 
zdim               : 6.55s     | [Perl + regexp+dict] 
gregory1B          : 45.36s    | [parallel + grep -E -f [strict]] 
oliv               : 60.35s    | [python + compiled regex + re.search()] 
BOC1B              : 74.71s    | grep -E [strict] 
codeforester_origB : 75.52s    | grep -E -f [strict] 
_

Remarque

Les solutions basées sur grep cherchaient une correspondance sur toute la ligne, donc dans ce cas elles contenaient des fausses correspondances: les méthodes _codeforester_orig_, _BOC1_, _BOC2_, _gregory1_, _inian4_ et oliv ont extrait 1 087 609 lignes sur 10 000 000 lignes, tandis que les autres méthodes ont extrait les 997 993 lignes correctes de _file2.txt_.

Remarques

  • J'ai testé cela sur mon ordinateur portable Ubuntu 16.10 (processeur Intel Core i7-7500U à 2,70 GHz)

  • L'étude complète de référence est disponible ici .

14
Håkon Hægland

Avez-vous essayé Awk qui pourrait accélérer un peu les choses:

awk 'FNR==NR{hash[$1]; next}{for (i in hash) if (match($0,i)) {print; break}}' file1.txt FS='|' file2.txt

(ou) en utilisant la fonction index() dans Awk comme suggéré par les commentaires de Benjamin W. , ci-dessous

awk 'FNR==NR{hash[$1]; next}{for (i in hash) if (index($0,i)) {print; break}}' file1.txt FS='|' file2.txt

(ou) une correspondance regex plus directe comme suggéré par Ed Morton dans les commentaires,

awk 'FNR==NR{hash[$1]; next}{for (i in hash) if ($0~i) {print; break}}' file1.txt FS='|' file2.txt

est tout ce dont vous avez besoin. Je suppose que ce sera plus rapide mais pas exactement sur les fichiers avec plus d'un million d'entrées. Ici, le problème réside dans la possibilité de faire correspondre n'importe où le long de la ligne. Si le même avait été dans une colonne particulière (par exemple, dites $2 Seul), une approche plus rapide pourrait être

awk 'FNR==NR{hash[$1]; next}$2 in hash' file1.txt FS='|' file2.txt

Vous pouvez également accélérer les choses en jouant avec l'ensemble locale de votre système. Paraphrasant ce merveilleux réponse de Stéphane Chazelas sur le sujet, vous pouvez accélérer les choses assez rapidement en définissant le passage des paramètres régionaux LC_ALL=C À la commande localement en cours d'exécution.

Sur tout système basé sur GNU, les valeurs par défaut pour locale

$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

Avec une variable LC_ALL, Vous pouvez définir toutes les variables de type LC_ À la fois dans un environnement local spécifié

$ LC_ALL=C locale
LANG=en_US.UTF-8
LC_CTYPE="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_COLLATE="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_PAPER="C"
LC_NAME="C"
LC_ADDRESS="C"
LC_TELEPHONE="C"
LC_MEASUREMENT="C"
LC_IDENTIFICATION="C"       
LC_ALL=C

Alors qu'est-ce que cela a un impact?

Autrement dit, lorsque vous utilisez le locale C, Il utilisera par défaut le langage de base Unix/Linux du serveur ASCII. Fondamentalement, lorsque vous grep quelque chose, par défaut, votre locale va être internationalisée et définie sur UTF-8, Qui peut représenter chaque caractère du jeu de caractères Unicode pour aider à afficher l'un des systèmes d'écriture du monde, actuellement plus de 110,000 caractères uniques, alors qu'avec ASCII chaque caractère est codé dans une séquence d'un seul octet et son jeu de caractères ne comprend pas plus de 128 caractères uniques.

Donc, cela se traduit par cela, lorsque vous utilisez grep sur un fichier codé dans le jeu de caractères UTF-8, Il doit faire correspondre chaque caractère avec l'un des cent mille caractères uniques, mais juste 128 dans ASCII, utilisez donc votre fgrep comme

LC_ALL=C fgrep -F -f file1.txt file2.txt

En outre, la même chose peut être adaptée à Awk, car elle utilise une correspondance regex avec l'appel match($0,i), la définition des paramètres régionaux C pourrait accélérer la correspondance de chaîne.

LC_ALL=C awk 'FNR==NR{hash[$1]; next}{for (i in hash) if (match($0,i)) {print; break}}' file1.txt FS='|' file2.txt
9
Inian

Hypothèses: 1. Vous souhaitez exécuter cette recherche uniquement sur votre poste de travail local. 2. Vous avez plusieurs cœurs/cpus pour profiter d'une recherche parallèle.

parallel --pipepart -a file2.txt --block 10M fgrep -F -f file1.txt

Quelques ajustements supplémentaires en fonction du contexte: A. Désactiver NLS avec LANG = C (cela est déjà mentionné dans une autre réponse) B. Définir un nombre maximum de correspondances avec l'indicateur -m.

Remarque: je suppose que le fichier 2 fait environ 4 Go et que la taille de bloc de 10 Mo est correcte, mais vous devrez peut-être optimiser la taille du bloc pour obtenir l'exécution la plus rapide.

6
gregory

Voici la solution Perl qui utilise Inline::C pour accélérer la recherche des champs correspondants dans le gros fichier:

use strict;
use warnings;
use Inline C => './search.c';

my $smallfile = 'file1.txt';
my $bigfile   = 'file2.txt';

open my $fh, '<', $smallfile or die "Can't open $smallfile: $!";
my %Word = map { chomp; $_ => 1 } <$fh>;
search( $bigfile, \%Word );

La sous-routine search() est implémentée en C pur en utilisant perlapi pour rechercher des clés dans le dictionnaire de petits fichiers %words:

search.c :

#include <stdio.h>
#include <sys/stat.h> 
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>


#define BLOCK_SIZE 8192       /* how much to read from file each time */
static char read_buf[BLOCK_SIZE + 1];

/*  reads a block from file, returns -1 on error, 0 on EOF, 
     else returns chars read, pointer to buf, and pointer to end of buf  */
size_t read_block( int fd, char **ret_buf, char **end_buf ) {
    int ret;
    char *buf = read_buf;
    size_t len = BLOCK_SIZE;
    while (len != 0 && (ret = read(fd, buf, len)) != 0) {
        if (ret == -1) {
            if (errno == EINTR)
                continue;
            perror( "read" );
            return ret;
        }
        len -= ret;
        buf += ret;
    }
    *end_buf = buf;
    *ret_buf = read_buf;
    return (size_t) (*end_buf - *ret_buf);
}

/* updates the line buffer with the char pointed to by cur,
   also updates cur
    */
int update_line_buffer( char **cur, char **line, size_t *llen, size_t max_line_len ) {
    if ( *llen > max_line_len ) {
        fprintf( stderr, "Too long line. Maximimum allowed line length is %ld\n",
                 max_line_len );
        return 0;
    }
    **line = **cur;
    (*line)++;
    (*llen)++;
    (*cur)++; 
    return 1;
}


/*    search for first pipe on a line (or next line if this is empty),
    assume line ptr points to beginning of line buffer.
  return 1 on success
  Return 0 if pipe could not be found for some reason, or if 
    line buffer length was exceeded  */
int search_field_start(
    int fd, char **cur, char **end_buf, char **line, size_t *llen, size_t max_line_len
) {
    char *line_start = *line;

    while (1) {
        if ( *cur >= *end_buf ) {
            size_t res = read_block( fd, cur, end_buf );        
            if (res <= 0) return 0;
        }
        if ( **cur == '|' ) break;
        /* Currently we just ignore malformed lines ( lines that do not have a pipe,
           and empty lines in the input */
        if ( **cur == '\n' ) {
            *line = line_start;
            *llen = 0;
            (*cur)++;
        }
        else {
            if (! update_line_buffer( cur, line, llen, max_line_len ) ) return 0;
        }
    }
    return 1;
}

/* assume cur points at starting pipe of field
  return -1 on read error, 
  return 0 if field len was too large for buffer or line buffer length exceed,
  else return 1
  and field, and  length of field
 */
int copy_field(
    int fd, char **cur, char **end_buf, char *field,
    size_t *flen, char **line, size_t *llen, size_t max_field_len, size_t max_line_len
) {
    *flen = 0;
    while( 1 ) {
        if (! update_line_buffer( cur, line, llen, max_line_len ) ) return 0;
        if ( *cur >= *end_buf ) {
            size_t res = read_block( fd, cur, end_buf );        
            if (res <= 0) return -1;
        }
        if ( **cur == '|' ) break;
        if ( *flen > max_field_len ) {
            printf( "Field width too large. Maximum allowed field width: %ld\n",
                    max_field_len );
            return 0;
        }
        *field++ = **cur;
        (*flen)++;
    }
    /* It is really not necessary to null-terminate the field 
       since we return length of field and also field could 
       contain internal null characters as well
    */
    //*field = '\0';
    return 1;
}

/* search to beginning of next line,
  return 0 on error,
  else return 1 */
int search_eol(
    int fd, char **cur, char **end_buf, char **line, size_t *llen, size_t max_line_len)
{
    while (1) {
        if ( *cur >= *end_buf ) {
            size_t res = read_block( fd, cur, end_buf );        
            if (res <= 0) return 0;
        }
        if ( !update_line_buffer( cur, line, llen, max_line_len ) ) return 0;
        if ( *(*cur-1) == '\n' ) {
            break;
        }
    }
    //**line = '\0'; // not necessary
    return 1;
}

#define MAX_FIELD_LEN 80  /* max number of characters allowed in a field  */
#define MAX_LINE_LEN 80   /* max number of characters allowed on a line */

/* 
   Get next field ( i.e. field #2 on a line). Fields are
   separated by pipes '|' in the input file.
   Also get the line of the field.
   Return 0 on error,
   on success: Move internal pointer to beginning of next line
     return 1 and the field.
 */
size_t get_field_and_line_fast(
    int fd, char *field, size_t *flen, char *line, size_t *llen
) {
    static char *cur = NULL;
    static char *end_buf = NULL;

    size_t res;
    if (cur == NULL) {
        res = read_block( fd, &cur, &end_buf );        
        if ( res <= 0 ) return 0;
    }
    *llen = 0;
    if ( !search_field_start( fd, &cur, &end_buf, &line, llen, MAX_LINE_LEN )) return 0;
    if ( (res = copy_field(
        fd, &cur, &end_buf, field, flen, &line, llen, MAX_FIELD_LEN, MAX_LINE_LEN
    ) ) <= 0)
        return 0;
    if ( !search_eol( fd, &cur, &end_buf, &line, llen, MAX_LINE_LEN ) ) return 0;
    return 1;
}

void search( char *filename, SV *href) 
{
    if( !SvROK( href ) || ( SvTYPE( SvRV( href ) ) != SVt_PVHV ) ) {
        croak( "Not a hash reference" );
    }

    int fd = open (filename, O_RDONLY);
    if (fd == -1) {
        croak( "Could not open file '%s'", filename );
    }
    char field[MAX_FIELD_LEN+1];
    char line[MAX_LINE_LEN+1];
    size_t flen, llen;
    HV *hash = (HV *)SvRV( href );
    while ( get_field_and_line_fast( fd, field, &flen, line, &llen ) ) {
        if( hv_exists( hash, field, flen ) )
            fwrite( line, sizeof(char), llen, stdout);
    }
    if (close(fd) == -1)
        croak( "Close failed" );

}

Les tests indiquent qu'elle est environ 3 fois plus rapide que la solution Perl pure la plus rapide (voir la méthode zdim2 Dans mon autre réponse ) présentée ici.

4
Håkon Hægland

Ce script Perl (a) génère un modèle d'expression régulière:

#!/usr/bin/Perl

use strict;
use warnings;

use Regexp::Assemble qw( );

chomp( my @ids = <> );
my $ra = Regexp::Assemble->new();
$ra->add(quotemeta($_)) for @ids;
print("^[^|]*\\|(?:" . (re::regexp_pattern($ra->re()))[0] . ")\\|");

Voici comment il peut être utilisé:

$ LC_ALL=C grep -P "$( a file1.txt )" file2.txt
date1|foo1|number1
date2|foo2|number2
date1|bar1|number1
date2|bar2|number2

Notez que le script utilise Regexp :: Assemble, vous devrez donc peut-être l'installer.

Sudo su
cpan Regexp::Assemble

Remarques:

  • Contrairement aux solutions baptisées BOC1, BOC2, codeforester_orig, gregory1, inian2, inian4 et oliv, ma solution gère correctement

    file1.txt
    foo1
    
    file2.txt
    date1|foo12|number5
    
  • Le mien devrait être meilleur que le similaire solution par @BOC car le modèle est optimisé pour réduire le retour en arrière. (Le mien fonctionne également s'il y a plus de trois champs dans file2.txt, alors que la solution liée peut échouer.)

  • Je ne sais pas comment cela se compare aux solutions split + dictionnaire.

4
ikegami

Voici une solution Python utilisant des ensembles - à peu près équivalente à une clé Perl uniquement un tableau de hachage ou awk dans le concept.

#!/usr/bin/python

import sys 

with open(sys.argv[1]) as f:
    tgt={e.rstrip() for e in f}

with open(sys.argv[2]) as f:
    for line in f:
        cells=line.split("|")
        if cells[1] in tgt:
            print line.rstrip()

Lorsque j'exécute cela sur des fichiers de taille similaire, cela s'exécute en environ 8 secondes.

Même vitesse que:

$ awk 'FNR==NR{arr[$1]; next} $2 in arr{print $0}' FS="|" /tmp/f1 /tmp/f2 

La solution Python et awk ne sont ici que des correspondances de chaînes complètes; pas une correspondance de style d'expression régulière partielle.

Étant donné que la solution awk est rapide et compatible POSIX, c'est la meilleure réponse.

3
dawg

Pouvez-vous essayer de join? Les fichiers doivent cependant être triés ...

$ cat d.txt
bar1
bar2
foo1
foo2

$ cat e.txt
date1|bar1|number1
date2|bar2|number2
date3|bar3|number3
date1|foo1|number1
date2|foo2|number2
date3|foo3|number3

$ join --nocheck-order -11 -22 -t'|' -o 2.1 2.2 2.3 d.txt e.txt
date1|bar1|number1
date2|bar2|number2
date1|foo1|number1
date2|foo2|number2

Petite mise à jour:
En utilisant LC_ALL = C devant la jointure, les choses accélèrent vraiment comme on peut le voir dans le benchmark de Håkon Hægland

PS1: J'ai des doutes si la jointure peut être plus rapide que grep -f ...

2
George Vasiliou

Bien que ce fil soit terminé, mais toutes les méthodes similaires à grep entre deux fichiers sont rassemblées dans cet article, pourquoi ne pas ajouter cette alternative awk, similaire (ou même améliorée) à la solution awk d'Inian gagnante de primes:

awk 'NR==FNR{a[$0]=1;next}a[$2]' patterns.txt FS="|" datafile.txt >matches.txt # For matches restricted on Field2 of datafile

Cela équivaut à Inian awk $2 in hash solution mais cela pourrait être encore plus rapide du fait que nous ne demandons pas à awk de vérifier si tout le tableau de hachage contient 2 $ de fichier2 - nous vérifions simplement si un [$ 2] a une valeur ou non.

Lors de la lecture du premier fichier de motifs, à part la création du tableau de hachage, nous attribuons également une valeur.

Si $2 du fichier de données avait été trouvé auparavant dans le fichier de signatures, puis a[$2] aurait une valeur et sera donc affiché car n'est pas nul.

si a[$2] du fichier de données ne renvoie aucune valeur (null) ceci est traduit en faux => pas d'impression.

Extension pour correspondre à l'un des trois champs du fichier de données:

awk 'NR==FNR{a[$0]=1;next}(a[$1] || a[$2] || a[$3])' patterns.txt FS="|" datafile.txt >matches.txt. #Printed if any of the three fields of datafile match pattern.

Dans les deux cas, appliquer LC_ALL = C devant awk, semble accélérer les choses.

PS1: Offcourse cette solution a aussi les pièges de toutes les solutions awk. N'est pas une correspondance de motifs. Est une correspondance directe/fixe entre les deux fichiers, comme la plupart des solutions ici.

PS2: Dans mon mauvais benchmark de machine en utilisant les petits fichiers de benchmark de Håkon Hægland , j'obtiens environ 20% de meilleures performances par rapport au awk 'FNR==NR{hash[$1]; next}$2 in hash' file1.txt FS='|' file2.txt

2
George Vasiliou

J'utiliserais SQLite3 :) Peut-être une base de données en mémoire ou autre chose. Importez les fichiers et utilisez la requête SQL.

1
user933161

Utilisation de flex:

1: construisez le processeur flex:

$ awk 'NR==1{ printf "%%%%\n\n.*\\|(%s",$0 } 
            { printf "|%s",$0 } 
       END  { print ")\\|.*\\n ECHO;\n.*\\n ;\n%%\n" }' file1.txt > a.fl

2: compilez-le

$ flex -Ca -F a.fl ; cc -O Lex.yy.c -lfl

3: et courir

$ a.out < file2.txt  > out

La compilation (cc ...) est un processus lent; cette approche ne paiera que pour les cas de file1.txt stable

(Dans ma machine) Le temps nécessaire pour exécuter un test de recherche "100 dans 10_000_000" dans cette approche est 3 fois plus rapide que LC_ALL=C fgrep...

1
JJoao

Vous pouvez également utiliser Perl pour cela:

Veuillez noter que cela encombrera la mémoire et que votre machine/serveur en a mieux.

Exemple de données:

%_STATION@gaurav * /root/ga/pl> head file1.txt file2.txt
==> file1.txt <==
foo1
foo2
...
bar1
bar2
...

==> file2.txt <==
date1|foo1|number1
date2|foo2|number2
date3|foo3|number3
...
date1|bar1|number1
date2|bar2|number2
date3|bar3|number3
%_STATION@gaurav * /root/ga/study/pl>

Sortie de script: Le script produira final sortie dans un fichier nommé output_comp.

%_STATION@gaurav * /root/ga/pl> ./comp.pl  file1.txt file2.txt ; cat output_comp
date1|bar1|number1
date2|bar2|number2
date2|foo2|number2
date1|foo1|number1
%_STATION@gaurav * /root/ga/pl>

Script:

%_STATION@gaurav * /root/ga/pl> cat comp.pl
#!/usr/bin/Perl

use strict ;
use warnings ;
use Data::Dumper ;

my ($file1,$file2) = @ARGV ;
my $output = "output_comp" ;
my %hash ;    # This will store main comparison data.
my %tmp ;     # This will store already selected results, to be skipped.
(scalar @ARGV != 2 ? (print "Need 2 files!\n") : ()) ? exit 1 : () ;

# Read all files at once and use their name as the key.
for (@ARGV) {
  open FH, "<$_" or die "Cannot open $_\n" ;
  while  (my $line = <FH>) {chomp $line ;$hash{$_}{$line} = "$line"}
  close FH ;
}

# Now we churn through the data and compare to generate
# the sorted output in the output file.
open FH, ">>$output" or die "Cannot open outfile!\n" ;
foreach my $k1 (keys %{$hash{$file1}}){
  foreach my $k2 (keys %{$hash{$file2}}){
    if ($k1 =~ m/^.+?$k2.+?$/) {
      if (!defined $tmp{"$hash{$file2}{$k2}"}) {
        print FH "$hash{$file2}{$k2}\n" ;
        $tmp{"$hash{$file2}{$k2}"} = 1 ;
      }
    }
  }
}
close FH  ;
%_STATION@gaurav * /root/ga/pl>

Merci.

1
User9102d82

À mon humble avis, grep est un bon outil hautement optimisé pour un énorme fichier2.txt mais peut-être pas pour autant de modèles à rechercher. Je suggère de combiner toutes les chaînes de file1.txt en une seule expression rationnelle énorme comme\| bar1 | bar2 | foo1 | foo2\|

echo  '\|'$(paste -s -d '|' file1.txt)'\|' > regexp1.txt

grep -E -f regexp1.txt file2.txt > file.matched

Et bien sûr, LANG = C peut aider. Veuillez donner votre avis ou envoyer vos fichiers afin que je puisse me tester.

1
BOC

Une façon possible est d'utiliser python:

$ cat test.py
import sys,re

with open(sys.argv[1], "r") as f1:
    patterns = f1.read().splitlines() # read pattern from file1 without the trailing newline

m = re.compile("|".join(patterns))    # create the regex

with open(sys.argv[2], "r") as f2:
    for line in f2: 
        if m.search(line) : 
            print line,               # print line from file2 if this one matches the regex

et utilisez-le comme ceci:

python test.py file1.txt file2.txt
1
oliv

définir la langue, etc. aide un peu, peut-être.

sinon, je ne peux pas penser à une solution magique pour échapper à votre problème de base: les données ne sont pas structurées, vous aurez donc une recherche qui se résume au nombre de lignes dans le fichier1 multiplié par le nombre de lignes dans le fichier2.

mettre le milliard de lignes dans une base de données et l'indexer de manière intelligente est la seule vitesse à laquelle je peux penser. cet indice devrait être très intelligent, bien que ......

La solution simple est: avoir suffisamment de mémoire pour tout contenir. sinon vous ne pouvez rien faire de plus à ce sujet ....

0
rens