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
3 Réponses :
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.
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.
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.
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
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.
+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,
Pouvez-vous décrire où se trouve [python-textprocessing] dans votre question? Je ne vois que les outils
bash
et unixWOW, 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 erreurawk: 1: caractère inattendu '.'
Oh,
gensub
est une fonction unique à GNUawk
. Je soupçonne que c'est la raison pour laquelle vous recevez l'erreur. Il y aura une solution de contournement en utilisant les fonctionsawk
régulières.