2
votes

Comment améliorer un script pour les recherches à partir de plusieurs fichiers CSV

J'ai besoin d'améliorer mon script ci-dessous, qui prend un fichier d'entrée contenant près d'un million de lignes uniques. Contre chaque ligne, il a des valeurs différentes dans 3 fichiers de recherche que j'ai l'intention d'ajouter dans ma sortie en tant que valeurs séparées par des virgules.

Le script ci-dessous fonctionne bien, mais il faut des heures pour terminer le travail. Je recherche une vraie solution rapide qui serait également moins lourde sur le système.

225OL0:LT1.PN1.ONT57, Up,Unlocked,Yes,12-533-4019,+123125334019,FD_BSFU.xml,+123125334819,FD_BSFU.xml
225OL0:LT1.PN1.ONT34, Up,Unlocked,Yes,12-530-2766,+123125302766,FD_BSFU.xml,
343OL5:LT1.PN1.ONT1, Down,Unlocked,No,,,
343OL5:LT1.PN1.ONT100, Down,Locked,No,,,
343OL5:LT1.PN1.ONT10,,,,
225OL0:LT1.PN1.ONT39, Up,Unlocked,Yes,,,

inputfile.csv contient les lignes ci-dessous:

343OL5:LT1.PON1.ONT100.SERV1,12-654-0330
343OL5:LT1.PON1.ONT100.C1.P1,12-654-0330
343OL5:LT7.PON8.ONT75.SERV1,12-664-1186
225OL0:LT1.PN1.ONT34.C1.P1.FLOW1,12-530-2766
225OL0:LT1.PN1.ONT57.C1.P1.FLOW1,12-533-4019

lookupfile1.csv contient:

225OL0:LT1.PN1.ONT34.C2.P1, +123125302766,REG,DigitMap,Unlocked,_media_BNT,FD_BSFU.xml,
225OL0:LT1.PN1.ONT57.C2.P1, +123125334019,REG,DigitMap,Unlocked,_media_BNT,FD_BSFU.xml,
225OL0:LT1.PN1.ONT57.C2.P2, +123125334819,REG,DigitMap,Unlocked,_media_BNT,FD_BSFU.xml,
343OL5:LT1.PN1.ONT100.C2.P11, +123128994019,REG,DigitMap,Unlocked,_media_ANT,FD_BSFU.xml,

lookupfile2.csv contient:

343OL5:LT1.PN1.ONT100, Down,Locked,No
225OL0:LT1.PN1.ONT57, Up,Unlocked,Yes
343OL5:LT1.PN1.ONT1, Down,Unlocked,No
225OL0:LT1.PN1.ONT34, Up,Unlocked,Yes
225OL0:LT1.PN1.ONT39, Up,Unlocked,Yes

lookupfile3.csv contient:

343OL5:LT1.PN1.ONT1
343OL5:LT1.PN1.ONT10
225OL0:LT1.PN1.ONT34
225OL0:LT1.PN1.ONT39
343OL5:LT1.PN1.ONT100
225OL0:LT1.PN1.ONT57

le résultat est:

#!/bin/bash
while read -r ONT
do
{

ONSTATUS=$(grep "$ONT," lookupfile1.csv | cut -d" " -f2)
CID=$(grep "$ONT." lookupfile3.csv | head -1 | cut -d, -f2)
line1=$(grep "$ONT.C2.P1," lookupfile2.csv | head -1 | cut -d"," -f2,7 | sed 's/ //')
line2=$(grep "$ONT.C2.P2," lookupfile2.csv | head -1 | cut -d"," -f2,7 | sed 's/ //')
echo "$ONT,$ONSTATUS,$CID,$line1,$line2" >> BUwithPO.csv
} & 
done < inputfile.csv


8 commentaires

Pouvez-vous décrire où se trouve [python-textprocessing] dans votre question? Je ne vois que les outils bash et unix


WOW, vous créez au moins 17 sous-shell appelant des utilitaires dans vos substitutions de commandes en liant les sorties et les entrées avec 9-pipes. Pas étonnant que ce soit lent. Pensez-y. Pour chaque itération (dans le million que vous avez), vous appelez 13-utilities dans la substitution de 4 commandes plus les tubes. Réduire cela en utilisant des composants intégrés, etc. devrait être l'objectif principal.


De plus, l'ordre des lignes de la sortie est-il important? Doit-il être dans le même ordre que celui que vous avez affiché, ou une ligne commençant par 343OL5 pourrait-elle être la première ligne?


L'ordre de @ DavidC.Rank n'est pas important, mais les colonnes doivent être dans le même ordre que celui décrit ci-dessus. pouvez-vous suggérer une alternative avec des éléments intégrés?


@AlexYu j'ai ajouté cette balise donc si quelqu'un peut suggérer une approche python


@Ibraheem - alors dans ce cas, il sera difficile de battre la solution awk . Avez-vous essayé ce que @tshiono a publié?


@ DavidC.Rankin Je viens de répondre, il semble qu'il manque quelque chose dans la solution awk, j'ai même ajouté #! / Usr / bin / awk et j'ai recommencé, puis cela a donné une erreur awk: 1: caractère inattendu '.'


Oh, gensub est une fonction unique à GNU awk . Je soupçonne que c'est la raison pour laquelle vous recevez l'erreur. Il y aura une solution de contournement en utilisant les fonctions awk régulières.


3 Réponses :


4
votes

Comme vous le verrez, le goulot d'étranglement exécutera grep dans la boucle plusieurs fois. Vous pouvez augmenter l'efficacité en créant une table de consultation avec des tableaux associatifs.
Si awk est disponible, essayez ce qui suit:

[Update]

#!/bin/bash

awk '
FILENAME ~ /lookupfile1.csv$/ {
    sub(",$", "", $1);
    onstatus[$1] = $2
}
FILENAME ~ /lookupfile2.csv$/ {
    split($2, a, ",")
    if (sub("\\.C2\\.P1,$", "", $1)) line1[$1] = a[1]","a[6]
    else if (sub("\\.C2\\.P2,$", "", $1)) line2[$1] = a[1]","a[6]
}
FILENAME ~ /lookupfile3.csv$/ {
    split($0, a, ",")
    if (match(a[1], ".+\\.ONT[0-9]+")) {
        ont = substr(a[1], RSTART, RLENGTH)
        cid[ont] = a[2]
    }
}
FILENAME ~ /inputfile.csv$/ {
    print $0","onstatus[$0]","cid[$0]","line1[$0]","line2[$0]
}
' /path/to/lookupfile1.csv /path/to/lookupfile2.csv /path/to/lookupfile3.csv /path/to/inputfile.csv > /path/to/BUwithPO.csv

{EDIT ]

Si vous avez besoin de spécifier des chemins absolus vers les fichiers, veuillez essayer:

#!/bin/bash

awk '
FILENAME=="lookupfile1.csv" {
    sub(",$", "", $1);
    onstatus[$1] = $2
}
FILENAME=="lookupfile2.csv" {
    split($2, a, ",")
    if (sub("\\.C2\\.P1,$", "", $1)) line1[$1] = a[1]","a[6]
    else if (sub("\\.C2\\.P2,$", "", $1)) line2[$1] = a[1]","a[6]
}
FILENAME=="lookupfile3.csv" {
    split($0, a, ",")
    if (match(a[1], ".+\\.ONT[0-9]+")) {
        ont = substr(a[1], RSTART, RLENGTH)
        cid[ont] = a[2]
    }
}
FILENAME=="inputfile.csv" {
    print $0","onstatus[$0]","cid[$0]","line1[$0]","line2[$0]
}
' lookupfile1.csv lookupfile2.csv lookupfile3.csv inputfile.csv > BUwithPO.csv

J'espère que cela vous aidera.


11 commentaires

J'ai essayé ci-dessus, et il dit awk: line 20: function gensub jamais défini


J'ai bien peur que votre version awk ne soit pas mise à jour. D'accord, je vais modifier le script sans utiliser la fonction.


J'ai mis à jour ma réponse en remplaçant la fonction gensub () . J'espère que cela fonctionnera maintenant.


@Ole Comment puis-je exécuter ce script en parallèle?


si je veux utiliser ce script awk dans un crontab, comment la variable FILENAME se comportera, comme je vois que les crontabs ont principalement besoin de chemins absolus, et je ne sais pas comment AWK interprétera si les noms de fichiers ont été donnés dans des variables car je ne veux pas coder en dur les noms de fichiers dans le script


Je reconnais que ce n'est pas une bonne manière d'incorporer des noms de fichiers dans le code. J'ai mis à jour ma réponse avec une petite amélioration en faisant correspondre les noms de fichiers avec regex. Vous pouvez maintenant spécifier les chemins absolus vers les fichiers en modifiant simplement la dernière ligne (liste d'arguments), même si vous devez toujours conserver les noms de fichiers tels quels. Concernant le parallèle , j'ai peur que GNU parallel ne soit pas appliqué pour l'application. Si vous avez encore besoin d'accélérer l'exécution, veuillez me faire part du benchmark actuel et du temps d'exécution cible. Je sais que mon code peut être amélioré. BR.


il n'y a pas de cible en tant que telle car je suis maintenant à moins de 10 secondes par rapport à mon script précédent qui prendrait au moins une demi-heure pour terminer. donc c'est déjà super rapide, mais si vous pouvez partager l'amélioration de mon apprentissage, je l'apprécierai vraiment


Une amélioration possible est de mettre l'instruction next à la fin de chaque bloc FILENAME ~ {} (juste avant l'accolade fermante). Il ignore les correspondances de noms de fichiers suivantes et augmentera légèrement l'efficacité. Une autre possibilité est d'ajuster les expressions régulières utilisées dans sub () et match () . Cela nécessite une approche de type essai et erreur. Ces améliorations sont facultatives si le script fonctionne assez rapidement car elles ajouteront des codes et risquent de perdre la maintenabilité. Ce sera une question commune de rentabilité.


Je vais essayer la déclaration suivante et vous informer du résultat


Je vois des problèmes avec les données de sortie. Il semble que les valeurs ne soient pas trouvées dans les fichiers de recherche, en particulier le lookupfile2 qui a deux valeurs à rechercher. Si line1 ne correspond pas et line2 le fait, il imprime la ligne 2 à la place de line1. Je veux mettre un champ vide (ou 0) pour les valeurs non trouvées. J'ai essayé else line [$ 1] = 0 "," 0 après les deux instructions if mais cela ne produit toujours pas la sortie souhaitée. Aucune suggestion?


D'accord, pourriez-vous mettre à jour votre question en ajoutant (mieux vaut ne pas écraser les textes existants) des exemples qui causent le problème ainsi que le résultat attendu pour ma meilleure compréhension? Désolé d'avoir pris votre temps. BR.



4
votes

Si, comme vous l'avez indiqué dans les commentaires, vous ne pouvez pas utiliser la solution fournie par @tshiono en raison d'un manque de gensub fourni par GNU awk , vous pouvez remplacer gensub avec deux appels à sub avec une variable temporaire pour accomplir le découpage du suffixe nécessaire.

Exemple:

awk '
FILENAME=="lookupfile1.csv" {
    sub(",$", "", $1);
    onstatus[$1] = $2
}
FILENAME=="lookupfile2.csv" {
    split($2, a, ",")
    if (sub("\\.C2\\.P1,$", "", $1)) line1[$1] = a[1]","a[6]
    else if (sub("\\.C2\\.P2,$", "", $1)) line2[$1] = a[1]","a[6]
}
FILENAME=="lookupfile3.csv" {
    split($0, a, ",")
#     ont = gensub("(\\.ONT[0-9]+).*", "\\1", 1, a[1])
    sfx = a[1]
    sub(/^.*[.]ONT[^.]*/, "", sfx)
    sub(sfx, "", a[1])
#     cid[ont] = a[2]
    cid[a[1]] = a[2]
}
FILENAME=="inputfile.csv" {
    print $0","onstatus[$0]","cid[$0]","line1[$0]","line2[$0]
}
' lookupfile1.csv lookupfile2.csv lookupfile3.csv inputfile.csv > BUwithPO.csv

J'ai commenté l'utilisation de gensub dans la partie relative à FILENAME == "lookupfile3.csv" et remplacé le gensub avec deux appels à sub en utilisant sfx (suffixe) comme variable temporaire.

Essayez et faites-moi savoir si vous peuvent l'utiliser.


3 commentaires

cela fonctionne comme prévu, à l'exception de quelques colonnes ici et là dans le désordre, ce qui pourrait être dû au fait que les fichiers de recherche ont le même désordre, je vais essayer d'améliorer les fichiers de recherche, merci pour l'aide mais je ne suis pas en mesure voter en raison du manque de points de réputation nécessaires :)


Pas de soucis, je suis content que cela ait aidé. awk devrait être ordres de grandeur plus rapide que votre script shell :)


En effet, c'est le cas :-) Je recherche une alternative python ou une base de données, mais je dois d'abord les apprendre car je ne suis qu'un débutant dans les scripts donc .. aura besoin d'un peu de temps



3
votes

Solution Perl

Le script suivant est similaire à la solution awk mais écrit en Perl. Enregistrez-le sous filter.pl et rendez-le exécutable.

./filter.pl lookupfile{1,2,3}.csv inputfile.csv > BUwithPO.csv

Exécutez-le comme ceci:

#!/usr/bin/env perl

use strict;
use warnings;

my %lookup1;
my %lookup2_1;
my %lookup2_2;
my %lookup3;

while( <> ) {
    if ( $ARGV eq 'lookupfile1.csv' ) {
        # 225OL0:LT1.PN1.ONT34, Up,Unlocked,Yes
        # ^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ 
        if (/^([^,]+),\s*(.*)$/) {
            $lookup1{$1} = $2;
        }
    } elsif ( $ARGV eq 'lookupfile2.csv' ) {
        # 225OL0:LT1.PN1.ONT34.C2.P1, +123125302766,REG,DigitMap,Unlocked,_media_BNT,FD_BSFU.xml,
        # ^^^^^^^^^^^^^^^^^^^^        ^^^^^^^^^^^^^                                  ^^^^^^^^^^^ 
        if (/^(.+ONT\d+)\.C2\.P1,\s*([^,]+),(?:[^,]+,){4}([^,]+)/) {
            $lookup2_1{$1} = "$2,$3";
        } elsif (/^(.+ONT\d+)\.C2\.P2,\s*([^,]+),(?:[^,]+,){4}([^,]+)/) {
            $lookup2_2{$1} = "$2,$3";
        }
    } elsif ( $ARGV eq 'lookupfile3.csv' ) {
        # 225OL0:LT1.PN1.ONT34.C1.P1.FLOW1,12-530-2766
        # ^^^^^^^^^^^^^^^^^^^^             ^^^^^^^^^^^
        if (/^(.+ONT\d+)[^,]+,\s*(.*)$/) {
            $lookup3{$1} = $2;
        }
    } else { # assume 'inputfile.csv'
        no warnings 'uninitialized'; # because not all keys ($_) have values in the lookup tables
        # 225OL0:LT1.PN1.ONT34
        chomp;
        print "$_,$lookup1{$_},$lookup3{$_},$lookup2_1{$_},$lookup2_2{$_}\n";        
    }
}

Il est important que les fichiers de recherche viennent en premier (comme dans les solutions awk , btw.) car ils construisent les quatre dictionnaires ( hachages en langage Perl) % lookup1 , % lookup2_1 , etc. puis les valeurs de inputfile.csv sont comparées à ces dictionnaires.


2 commentaires

+1; Il est intéressant de voir la même chose faire dans une autre langue, non seulement awk mais aussi perl . Cela aide à comprendre ce qui se passe.


Merci, j'ai épuisé la solution Perl aujourd'hui et cela fonctionne aussi comme prévu, je vais l'exécuter encore plusieurs fois pour faire une comparaison entre les solutions Perl et awk fournies ici,