grep avec motif d'un file (3.2Gb) correspondant dans un autre file (4.8Gb)

J'ai deux files text. L'un est un file text avec le nom, l'adresse e-mail et d'autres champs. Certaines lignes de file1 :

 John:[email protected]:johnson123:22hey Erik:[email protected]:johnson133:22hey Robert:[email protected]:johnson123:21hey Johnnny:[email protected]:johnson123:22hey 

L'autre contient uniquement des adresses e-mail. Exemples de file2 :

 [email protected] [email protected] [email protected] [email protected] 

Je veux que la sortie soit chaque ligne complète de file1 qui a une adresse email dans le file2 . Par exemple, [email protected] est dans file2 , donc j'aimerais voir la ligne suivante de file1 :

 John:[email protected]:johnson123:22hey 

Existe-t-il un moyen facile de searchr file1 et de sortir les lignes qui correspondent à la "list des adresses électroniques" file2 ?

J'ai cherché HEURES, mais mes searchs Google (et les searchs StackOverflow) ainsi que les efforts sur la command line n'ont pas été efficaces jusqu'à présent.

Commandes J'ai essayé et pense fonctionner:

 fgrep -f file2.txt file1.txt > matched.txt grep -F -f .... grep -F -x -f file1 file2 > common 

etc, mais ils ont tous la grep memory exhausted – les files que je suis correspondent sont 4.8GB ( file1 ) et 3.2GB ( file2 , contenant seulement les adresses e-mail). Je suppose que la memory s'épuise avec ces commands. J'ai trouvé une méthode utilisant find pour exécuter les commands plus lisse je suppose, mais ne l'a pas fait fonctionner.

tldr ; besoin de faire correspondre file2 avec file1 et s'il y a une ligne de file2 qui correspond à une ligne dans file1 , le sortir. Les files sont volumineux et j'ai besoin d'un moyen sûr de ne pas utiliser toute la memory.

Merci, cherché toute la journée pour cela et expérimenté, je ne voulais pas abandonner (5hours +).

Il est plutôt difficile de faire fonctionner de gros files mais vous pouvez le faire en 3 étapes:

  1. Trier le file1 par second champ

     sort -k2,2 -t: file1 >file1.sorted 
  2. Trier le file2

     sort file2 >file2.sorted 
  3. Joindre 2 files par champ email

     join -t: -2 2 file2.sorted file1.sorted -o 2.1,0,2.3,2.4 >matched.txt 

Je soumets une deuxième réponse à cette question (c'est un problème intéressant). Celui-ci est totalement différent de ma solution SQLite et des solutions de sort + join plutôt prometteuses qui commencent à apparaître:

En utilisant votre approche initiale avec grep -f , mais littéralement réduire le problème un peu. Divisons le "file de requête", file2 en split gérables en utilisant split .

L'utilitaire de split est capable de split un file en un certain nombre de files plus petits en fonction d'un nombre de lignes.

Un file de 3,2 Go avec une longueur de ligne moyenne de 20 caractères a quelque part environ 172 000 000 de lignes (à less que j'aie fait une erreur d'arithmétique). La division en 2000 files de 85000 lignes par file est faisable.

Alors,

 $ mkdir testing $ cd testing $ split -l 85000 -a 4 ../file2 

L'option -a 4 indique à split d'utiliser quatre caractères après un x initial pour créer les noms de files pour les nouveaux files. Les files seront appelés xaaaa , xaaab , etc.

Ensuite, exécutez le grep -f original sur ceux-ci:

 for f in x????; do grep -F -f "$f" ../file1 done 

Cela peut rendre grep capable de contenir l'set maintenant beaucoup plus petit de templates de requête en memory.

UPDATE : Avec 145 526 885 lignes, utilisez split -l 72000 -a 4 pour créer environ 2000 files.

N'oubliez pas d'effacer le directory de testing chaque fois que vous essayez de créer un nouvel set de files fractionnés.

Notez que les files partagés de cette réponse sont individuellement utilisables en tant qu'input à l'une des autres réponses que vous pouvez get à cette question.

Réponse de Costas est probablement le meilleur count tenu de votre problème exact, car vous avez un champ qui a une correspondance à 100%.

Mais si votre problème était de grepping pour des millions de regexps en milliards de lignes, GNU Parallel a une description de la façon de le faire: https://www.gnu.org/software/parallel/man.html#EXAMPLE:-Grepping -n-lignes-pour-m-expressions-régulières

La solution la plus simple pour grep un gros file pour beaucoup de regexps est:

 grep -f regexps.txt bigfile 

Ou si les expressions rationnelles sont des strings fixes:

 grep -F -f regexps.txt bigfile 

Il y a 3 facteurs limitants: CPU, RAM et E / S disque.

La RAM est facile à mesurer: si le process grep occupe la plus grande partie de votre memory libre (par exemple lorsque vous exécutez en haut), la RAM est un facteur limitant.

Le CPU est aussi facile à mesurer: si le grep prend plus de 90% de CPU en haut, alors le CPU est un facteur limitant, et la parallélisation accélérera cela.

Il est plus difficile de voir si les E / S du disque sont le facteur limitant et, en fonction du système de disque, il peut être plus rapide ou plus lent de paralléliser. La seule façon de savoir avec certitude est de tester et de mesurer.

Facteur limitatif: RAM

Le file grep -f regexs.txt fonctionne normalement, peu importe la taille de bigfile, mais si regexps.txt est si gros qu'il ne peut pas tenir dans la memory, vous devez split cela.

grep -F prend environ 100 octets de RAM et grep prend environ 500 octets de RAM par 1 octet de regexp. Donc, si regexps.txt est 1% de votre RAM, il peut être trop gros.

Si vous pouvez convertir vos expressions rationnelles en strings fixes, faites-le. Par exemple, si les lignes que vous searchz dans bigfile ressemblent à ceci:

 ID1 foo bar baz Identifier1 quux fubar ID2 foo bar baz Identifier2 

alors votre regexps.txt peut être converti à partir de:

 ID1.*Identifier1 ID2.*Identifier2 

dans:

 ID1 foo bar baz Identifier1 ID2 foo bar baz Identifier2 

De cette façon, vous pouvez utiliser grep -F qui prend environ 80% de memory en less et est beaucoup plus rapide.

Si cela ne rentre toujours pas dans la memory, vous pouvez le faire:

 parallel --pipepart -a regexps.txt --block 1M grep -F -f - -n bigfile | sort -un | perl -pe 's/^\d+://' 

Le 1M devrait être votre memory libre divisée par le nombre de coeurs et divisée par 200 pour grep -F et par 1000 pour grep normal. Sur GNU / Linux, vous pouvez faire:

 free=$(awk '/^((Swap)?Cached|MemFree|Buffers):/ { sum += $2 } END { print sum }' /proc/meminfo) percpu=$((free / 200 / $(parallel --number-of-cores)))k parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - -n bigfile | sort -un | perl -pe 's/^\d+://' 

Si vous pouvez vivre avec des lignes dupliquées et un ordre erroné, c'est plus rapide à faire:

 parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - bigfile 

Facteur limitatif: CPU

Si le CPU est le facteur limitant, la parallélisation doit être faite sur les expressions rationnelles:

 cat regexp.txt | parallel --pipe -L1000 --round-robin --compress grep -f - -n bigfile | sort -un | perl -pe 's/^\d+://' 

La command commencera un grep par CPU et lira le bigfile une fois par CPU, mais comme cela se fait en parallèle, toutes les lectures sauf la première seront mises en cache dans la RAM. Selon la taille de regexp.txt, il peut être plus rapide d'utiliser –block 10m au lieu de -L1000.

Certains systèmes de stockage fonctionnent mieux en lisant plusieurs morceaux en parallèle. Cela est vrai pour certains systèmes RAID et pour certains filesystems réseau. Pour paralléliser la lecture de bigfile:

 parallel --pipepart --block 100M -a bigfile -k --compress grep -f regexp.txt 

Cela splita bigfile en morceaux de 100 Mo et exécutera grep sur chacun de ces morceaux. Pour paralléliser la lecture de bigfile et de regexp.txt, combinez les deux en utilisant –fifo:

 parallel --pipepart --block 100M -a bigfile --fifo cat regexp.txt \ \| parallel --pipe -L1000 --round-robin grep -f - {} 

Si une ligne correspond à plusieurs expressions rationnelles, la ligne peut être dupliquée.

Plus gros problème

Si le problème est trop grand pour être résolu par cela, vous êtes probablement prêt pour Lucene.

Avertissement important: J'ai testé ceci sur datatables fournies dans la question. Le chargement de plusieurs gigaoctets de données dans une database SQLite peut prendre beaucoup de time. L'interrogation à l'aide de deux champs de text peut être inefficace. La performance du disque peut prendre en count. Etc. etc.

Le script sh suivant créera la base de database.db SQLlite database.db (ce file sera supprimé s'il existe déjà), créera les tables qadr et data et chargera datatables dans les deux tables ( file1 en data et file2 en qadr ). Il créera alors un index sur data.adr .

 #!/bin/sh address_file="file2" data_file="file1" database="database.db" rm -f "$database" sqlite3 "$database" <<END_SQL CREATE TABLE qadr ( adr TEXT ); CREATE TABLE data ( name TEXT, adr TEXT, tag1 TEXT, tag2 TEXT ); .separator : .import "$data_file" data .import "$address_file" qadr VACUUM; CREATE UNIQUE INDEX adri ON data(adr); VACUUM; END_SQL 

La création de l'index suppose que les adresses dans le file1 sont uniques (c'est-à-dire que le deuxième champ : -delimited est unique). S'ils ne le sont pas, supprimez UNIQUE de l'instruction CREATE INDEX (idéalement, ils sont uniques et, idéalement, les lignes du file2 sont également uniques).

Je n'ai jamais travaillé avec SQLite et ces quantités de données, mais je sais que les imports de plusieurs gigaoctets dans MongoDB et MySQL peuvent être douloureusement lentes et que la création d'index peut prendre beaucoup de time. Donc, ce que je dis essentiellement, c'est que je jette ça pour quelqu'un qui a beaucoup de données à tester.

Ensuite, il s'agit d'une simple requête:

 $ sqlite3 database.db 'SELECT data.* FROM data JOIN qadr ON (data.adr = qadr.adr)' John|[email protected]|johnson123|22hey 

ou peut-être même juste

 $ sqlite3 database.db 'SELECT * FROM data NATURAL JOIN qadr' John|[email protected]|johnson123|22hey 

Quelqu'un avec plus de connaissances SQLite va sûrement donner un commentaire constructif à ce sujet.

Si vous avez besoin d'éviter une solution de database (je ne sais pas pourquoi), vous pouvez le faire en sortingant les deux files sur les adresses électroniques, puis en utilisant la command de join , qui se rapproche de ce que ferait une database.

Voici ce que j'ai fait:

 sort -t: +1 file1 -o file1 sort file2 -o file2 join -t: -o 1.1,1.2,1.3,1.4 -1 2 file1 file2 

Cela semble faire la bonne chose avec vos données d'échantillon. Il sortinge les files en place . Si vous ne le souhaitez pas, changez l'option -o de la sort des noms de files temporaires, puis utilisez ceux de la jointure. En outre, si vous n'avez pas 4 champs dans le premier file, vous devez le prendre en count dans l'option -o pour join .

Pour plus de détails, consultez les pages de manuel.

Quelque chose comme ça fonctionnerait, mais je ne suis pas sûr que ce soit une bonne idée en fonction de votre cas d'utilisation (non testé):

 while read f2line do f1=$(grep $line file1) [[ ! -z $f1 ]] && echo $f1line done < file2 

Une autre solution possible si vous voulez plus d'une méthode à une seule ligne (testée rapidement ci-dessous):

 grep . file2 | xargs -i^ grep ^ file1 

Ce qui a donné:

 root@7Z233W1 (/tmp)# cat f1 John:[email protected]:johnson123:22hey Erik:[email protected]:johnson133:22hey Robert:[email protected]:johnson123:21hey Johnnny:[email protected]:johnson123:22hey root@7Z233W1 (/tmp)# cat f2 [email protected] [email protected] [email protected] [email protected] root@7Z233W1 (/tmp)# grep . f2 | xargs -i^ grep ^ f1 John:[email protected]:johnson123:22hey 

Voici une version du script de Kusalananda qui utilise perl pour transformer le file1 de : séparé en TAB séparés avant de le sqlite3 dans sqlite3 .

Le script perl embedded vérifie s'il y a 5 champs au lieu de 4. Si c'est le cas, il ajoute le champ 3 au champ 2 (restauration du : qui a été supprimé par le découpage automatique), puis supprime le champ 3.

 #!/bin/sh address_file="file2" data_file="file1" database="database.db" rm -f "$database" sqlite3 "$database" <<END_SQL CREATE TABLE qadr ( adr TEXT ); CREATE TABLE data ( name TEXT, adr TEXT, tag1 TEXT, tag2 TEXT ); .mode line .import "$address_file" qadr END_SQL perl -F: -lane 'if (@F == 5) { $F[1] .= ":" . $F[2]; # perl arrays are zero-based delete $F[2]; }; print join("\t",@F);' $data_file | sqlite3 "$database" -separator $'\t' '.import /dev/stdin data' sqlite3 "$database" <<END_SQL VACUUM; CREATE UNIQUE INDEX adri ON data(adr); VACUUM; END_SQL 

IMO, sqlite ne convient pas pour une database aussi grande. Je recommand d'utiliser mysql ou postgresql place. Pour ce genre de tâche, la vitesse brute de mysql fait probablement un meilleur choix – c'est plus rapide pour des choses simples comme celle-ci mais postgresql est beaucoup plus rapide pour des tâches plus complexes – mon expérience pg est "smart fast" mysql est «stupide» (c.-à-d., il travaille dur, sans beaucoup de capacité à travailler intelligemment).

Le script ci-dessus peut facilement être adapté pour fonctionner avec les clients de command line psql ou mysql au lieu de sqlite3 , mais je modifierais les commands CREATE TABLE pour utiliser la taille fixe CHARACTER(size) au lieu de TEXT , où la size est une estimation raisonnable à quelle est la taille maximale de chaque champ – par exemple peut-être 255 caractères pour le champ adr et 10-50 caractères pour les autres.

une optimization possible consiste à sélectionner les tailles de champs de façon à ce que chaque logging soit un diviseur pair de la taille de bloc de votre lecteur (en tenant count du time de réponse par logging de mysql / postgresql). 512 octets devrait être bon pour toutes les tailles de blocs courants. faire les champs quelle que soit la taille dont vous avez besoin, et append un champ CHARACTER(size) supplémentaire, non utilisé pour compenser la différence. Le but est de faire en sorte que les loggings ne franchissent jamais une limite de bloc, de sorte que le moteur DB ne doit jamais lire dans un bloc de disque pour get toutes datatables pour un logging donné (en fait, il va lire plusieurs loggings dans un bloc avec la taille de bloc la plus courante, mais qui ne fait que consortingbuer à la performance, ne peut pas le nuire).

https://dba.stackexchange.com/ est probablement le meilleur site pour searchr ou requestr des informations sur l'optimization de la taille des loggings.