[Shell] addition de champs

addition de champs [Shell] - Shell/Batch - Programmation

Marsh Posté le 22-06-2006 à 22:55:19    

Bonsoir,
 
je génère automatiquement un fichier trié en fonction du premier champ qui ressemble à:
 
a;3
a;2.078
a;3
b;65
b;0.54
 
Il y a énormement de "1er champ" différent et le fichier comprend + de 100000 entrées.  
Je voudrais faire la somme des chiffres correspondant au même 1er champ et afficher le résultat sous la forme:
 
a 8.078
b 65.54
 
j'ai tenté des cat | cut ou encore cat | awk -F";" 'sum+=$2' mais ce n'est pas concluant.
est-ce que vous avez des pistes pour résoudre ce problème?
 
toutes les idées seront les bienvenues  :)

Message cité 1 fois
Message édité par batch_warrior le 22-06-2006 à 23:00:00
Reply

Marsh Posté le 22-06-2006 à 22:55:19   

Reply

Marsh Posté le 22-06-2006 à 23:37:02    

batch_warrior a écrit :

Bonsoir,
 
je génère automatiquement un fichier trié en fonction du premier champ qui ressemble à:
 
a;3
a;2.078
a;3
b;65
b;0.54
 
Il y a énormement de "1er champ" différent et le fichier comprend + de 100000 entrées.  
Je voudrais faire la somme des chiffres correspondant au même 1er champ et afficher le résultat sous la forme:
 
a 8.078
b 65.54
 
j'ai tenté des cat | cut ou encore cat | awk -F";" 'sum+=$2' mais ce n'est pas concluant.
est-ce que vous avez des pistes pour résoudre ce problème?
 
toutes les idées seront les bienvenues  :)


 
Pas de pb... à condition expresse que le fichier soit vraiment trié (bon, sinon c'est pas bien grave mais faut le trier dans le script)

#!/bin/sh
 
# On crée un fichier de travail copie exacte du fichier d'origine
dir_tmp="$HOME/tmp"
name_tmp="`basename $0 .sh`_work.$$"
file_tmp="$dir_tmp/$name_tmp"
cp fichier "$file_tmp"   # Ici, on peut le trier s'il ne l'est pas déjà
 
# On rajoute un élément final qu fichier de travail
echo "_EOF_" >>"$file_tmp"
 
# On stocke le fichier de travail dans le canal 3
exec 3<"$file_tmp"
 
# On traite le canal 3 ligne par ligne
while read lig 0<&3
do
    # On découpe la ligne en "id + valeur"
    id=`echo $ligne |cut -f1 -d\;`
    valeur=`echo $ligne |cut -f2 -d\;`
 
    # Si l'id est différent du précédent (on a changé d'id)
    if test "$id" != "$mem_id"
    then
        # Si le précédent id existe (on n'est pas sur le premier)
        if test -n "$mem_id"
        then
              # On affiche l'id + compteur
              echo "$mem_id $cpt"
        fi
 
        # On mémorise l'id pour le nouveau bloc - RAZ des compteurs
        mem_id=$id
        cpt=0
    fi
 
    # on incrémente le compteur avec la valeur
    cpt=`expr $cpt + $valeur`
done
 
# On efface le fichier temporaire
rm -f "$file_tmp"


 
Principe de ce script
Je vais traiter le fichier ligne par ligne. A chaque ligne, je vérifie si l'id a changé par rapport à la ligne d'avant. Si c'est le cas et s'il y avait un id sur la ligne d'avant, c'est qu'on n'est pas sur la première ligne donc qu'il y a un compteur à afficher pour le bloc précédent. De toute façon, comme on est sur un nouveau bloc, je mémorise mon identifiant et réinitialise mon compteur.
Ensuite, à chaque lieng, j'ajoute la valeur au total déjà accumulé.
 
La chaine "EOF" ajoutée à la fin du fichier sert pour pouvoir afficher le dernier bloc accumulé (puisque, quand le script rencontrera "EOF", il aura forcément "id" différent de "mem_id" ). Sans ce "EOF", je ne pourrais pas afficher le dernier total. Et comme je rajoute un élément au fichier d'origine, je suis obligé d'utliser un fichier de travail temporaire pour ne pas polluer le fichier d'origine.
Ce fichier temporaire doit impérativement être unique (on sait jamais, deux utilisateurs différents ou même moi je pourrais lancer ce script plusieurs fois en parallèle et il ne faut pas qu'il y ait collision dans le fichier temporaire) donc j'inclus dans son nom la variable "$$" identifiant mon pid. J'y inclus aussi le nom du script comme ça, ce fichier est facile à retrouver et à nettoyer si le script s'interromp brutalement avant la fin.
 
la syntaxe "exec 3<fic" permet de créer un nouveau canal d'entrée qui contient l'intégralité du fichier. Ensuite, chaque "while read ... 0<&3" va y lire une ligne à la fois et sortira lorsque tout aura été lu.
 
J'espère avoir été clair. Mais c'est dommage de le faire en shell pour un truc de 100000 entrées (long long long, au-moins 20 minutes) alors que Python pourrait te traiter ça en 3 secondes. Le pb, c'est que je ne connais pas assez bien Python (j'ai commencé il y a à peine 3 jours) pour te fournir la solution. Ptet tu pourrais aller poster ton truc dans le forum Python...


Message édité par Sve@r le 22-06-2006 à 23:40:28

---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 23-06-2006 à 10:54:35    

merci bcp pour cette réponse rapide de détaillée!!!
 
Malheureusement, les performances ne sont pas au rdv. Je vais exploiter la piste python
 

Reply

Marsh Posté le 23-06-2006 à 11:31:32    

python est une bonne piste en effet pour certains travaux surtout que là tu n'as pas forcement besoin de beaucoup de librairies. En plus les dictionnaires sont sympa. mais il a aussi perl, qui possède l'avantage de compiler le code avant execution, et non pas de l'interpréter. facile d'accès, ya plein de tuto, et si tu as besoin de "perf" sans nécessairement passer par du C/C++  . ça peut etre une autre piste

Message cité 1 fois
Message édité par youx21 le 23-06-2006 à 11:32:30
Reply

Marsh Posté le 23-06-2006 à 17:06:53    

youx21 a écrit :

python est une bonne piste en effet pour certains travaux surtout que là tu n'as pas forcement besoin de beaucoup de librairies. En plus les dictionnaires sont sympa. mais il a aussi perl, qui possède l'avantage de compiler le code avant execution, et non pas de l'interpréter. facile d'accès, ya plein de tuto, et si tu as besoin de "perf" sans nécessairement passer par du C/C++  . ça peut etre une autre piste


 
Je croyais que Python était la suite de Perl ???
 
En ce qui concerne les perfs, j'avais fait un script shell qui exploite un fichier ligne par ligne, chaque ligne contenant plein d'infos séparées par des ";" (un bête CSV en fait).
Chaque ligne faisant référence à un élément devant être écrite dans un format XML (avec une quarantaine de ligne par élément).
Mon fichier initial faisait 8800 lignes. Avec une boucle pour traiter chaque ligne plus une quarantaine d'echo dans la boucle, mon script a traité le fichier et généré le XML en 1h50.
Ensuite, tout content d'avoir appris comment ouvrir, lire et écrire un fichier en Python, j'ai porté mon script sous Python juste pour voir. Il a traité le fichier et écrit le XML en 21 secondes !!!
Bon, le gap de perfos est évidemment démesuré et ce ne sera pas toujours le cas. Là, je présume que mon script shell est ralenti par tous ses "echo >>" qu'il fait pour chaque élément (accès disque pour chaque "echo" )... alors qu'il est probable que Python passe par des buffer mémoire lorsque il traite le "fp.fwrite()". Mais je pense que pour le pb de batch_warrior, Python vs bash ya pas photo (je ne dis rien sur Perl, je ne le connais hélas pas)...


---------------
Vous ne pouvez pas apporter la prospérité au pauvre en la retirant au riche.
Reply

Marsh Posté le 23-06-2006 à 18:07:06    

pour le fun je propose ce script qui prend en argument ton fichier :
 

Code :
  1. #!/bin/ksh
  2. VAR=$(cat $1 | cut -d";" -f1| sort -u)
  3. for i in $VAR
  4. do
  5.         eval $i=0
  6. done
  7. cat b | tr ";" " " | while read v valeur
  8. do
  9.         eval $v="\$$v+$valeur"
  10. done
  11. for i in $VAR
  12. do
  13.         echo $i=$( eval echo "\$$(echo $i)"| bc)
  14. done


 
 
ATTENTION
j ai fait fonctionner ce script sous AIX, mais il ne marche pas sous cygwin
voir ce sujet de discussion pour comprendre pourquoi :
http://forum.hardware.fr/hardwaref [...] 2735-1.htm
 
(en fait c est a cause du pipe avant le while ....)
 
 
dans mon code j ai du faire 2 passage sur le fichier, mais y a moyen d'en faire qu'un seul....


Message édité par gloo le 23-06-2006 à 18:08:13
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed