web-dev-qa-db-fra.com

Rechercher le contenu d'un fichier dans un autre fichier sous UNIX

J'ai 2 fichiers. Le premier fichier contient la liste des ID de ligne des tuples d'une table de la base de données . Et le second fichier contient des requêtes SQL avec ces ID de ligne dans la clause "Where" de la requête.

Par exemple:

Fichier 1

1610657303
1610658464
1610659169
1610668135
1610668350
1610670407
1610671066

Fichier 2

update TABLE_X set ATTRIBUTE_A=87 where ri=1610668350;
update TABLE_X set ATTRIBUTE_A=87 where ri=1610672154;
update TABLE_X set ATTRIBUTE_A=87 where ri=1610668135;
update TABLE_X set ATTRIBUTE_A=87 where ri=1610672153;

Je dois lire le fichier 1 et rechercher dans le fichier 2 toutes les commandes SQL qui correspondent à l'ID de ligne du fichier 1 et vider ces requêtes SQL dans un troisième fichier.

Le fichier 1 contient 1,00,000 entrées et le fichier 2 contient 10 fois les entrées du fichier 1, soit 1,00,0000.

J'ai utilisé grep -f File_1 File_2 > File_3. Mais cela est extrêmement lent et le taux est de 1000 entrées par heure.

Y at-il un moyen plus rapide de faire cela?

26
Tirthankar

Une manière avec awk:

awk -v FS="[ =]" 'NR==FNR{rows[$1]++;next}(substr($NF,1,length($NF)-1) in rows)' File1 File2

Cela devrait être assez rapide. Sur ma machine, il a fallu moins de 2 secondes pour créer une recherche de 1 million d'entrées et la comparer à 3 millions de lignes. 

Spécifications de la machine:

Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz (8 cores)
98 GB RAM
17
jaypal singh

Vous n'avez pas besoin d'expression régulière, donc grep -F -f file1 file2

29
fizzer

Les solutions awk/grep mentionnées ci-dessus étaient lentes ou gourmandes en mémoire sur ma machine (fichier1 10 ^ 6 lignes, fichier2 10 ^ 7 lignes). Je suis donc venu avec une solution SQL utilisant sqlite3.

Transformez fichier2 en un fichier au format CSV où le premier champ est la valeur après ri=

cat file2.txt  | gawk -F= '{ print $3","$0 }' | sed 's/;,/,/' > file2_with_ids.txt

Créez deux tables:

sqlite> CREATE TABLE file1(rowId char(10));
sqlite> CREATE TABLE file2(rowId char(10), statement varchar(200));

Importez les identifiants de ligne de fichier1:

sqlite> .import file1.txt file1

Importez les déclarations de file2 en utilisant la version "préparée":

sqlite> .separator ,
sqlite> .import file2_with_ids.txt file2

Sélectionnez toutes et seulement les instructions de la table file2 avec un ID de ligne correspondant dans la table file1:

sqlite> SELECT statement FROM file2 WHERE file2.rowId IN (SELECT file1.rowId FROM file1);

Le fichier 3 peut être facilement créé en redirigeant la sortie vers un fichier avant d'émettre l'instruction select:

sqlite> .output file3.txt

Données de test:

sqlite> select count(*) from file1;
1000000
sqlite> select count(*) from file2;
10000000
sqlite> select * from file1 limit 4;
1610666927
1610661782
1610659837
1610664855
sqlite> select * from file2 limit 4;
1610665680|update TABLE_X set ATTRIBUTE_A=87 where ri=1610665680;
1610661907|update TABLE_X set ATTRIBUTE_A=87 where ri=1610661907;
1610659801|update TABLE_X set ATTRIBUTE_A=87 where ri=1610659801;
1610670610|update TABLE_X set ATTRIBUTE_A=87 where ri=1610670610;

Sans créer d’index, l’instruction select a pris environ 15 secondes sur une machine AMD A8 1.8HGz 64bit Ubuntu 12.04.

1
Marcello Romani

Je suggère d'utiliser un langage de programmation tel que Perl, Ruby ou Python.

Dans Ruby, une solution lisant les deux fichiers (f1 et f2) une seule fois pourrait être:

idxes = File.readlines('f1').map(&:chomp)

File.foreach('f2') do | line |
  next unless line =~ /where ri=(\d+);$/
  puts line if idxes.include? $1
end

ou avec Perl

open $file, '<', 'f1';
while (<$file>) { chomp; $idxs{$_} = 1; }
close($file);

open $file, '<', 'f2';
while (<$file>) {
    next unless $_ =~ /where ri=(\d+);$/;
    print $_ if $idxs{$1};
}
close $file;
1
undur_gongor

Peut-être essayer AWK et utiliser le numéro du fichier 1 comme clé pour un script simple, par exemple

Le premier script produira le script awk:
awk -f script1.awk

 {
 print "\ $ 0 ~", $ 0, "{print\$ 0}"> script2.awk; 
 } 

puis invoquez script2.awk avec le fichier

0
Damian0o

La plupart des réponses précédentes sont correctes, mais la seule chose qui a fonctionné pour moi a été cette commande 

grep -oi -f a.txt b.txt

 enter image description here

0
grepit

Il se peut que je manque quelque chose, mais ne serait-il pas suffisant de simplement réitérer les ID dans file1 et pour chaque ID, grep file2 et de stocker les correspondances dans un troisième fichier? C'est à dire.

 for ID in `cat file1`; do grep $ID file2; done > file3

Ce n’est pas très efficace (car le fichier 2 sera lu encore et encore), mais cela peut vous suffire. Si vous voulez plus de rapidité, je suggérerais d'utiliser un langage de script plus puissant qui vous permette de lire file2 sur une carte, ce qui permet d'identifier rapidement les lignes d'un identifiant donné.

Voici une version Python de cette idée:

queryByID = {}

for line in file('file2'):
  lastEquals = line.rfind('=')
  semicolon = line.find(';', lastEquals)
  id = line[lastEquals + 1:semicolon]
  queryByID[id] = line.rstrip()

for line in file('file1'):
  id = line.rstrip()
  if id in queryByID:
    print queryByID[id]
0
Frerich Raabe