BASH : Comparer 2 fichiers et afficher les similarités

BASH : Comparer 2 fichiers et afficher les similarités - Codes et scripts - Linux et OS Alternatifs

Marsh Posté le 04-02-2015 à 09:26:18    

Bonjour, je débute en BASH et dois réaliser une opération à l'aide d'un script.
Le problème étant le suivant :  
J'ai 2 fichiers "queue -p" d'un serveur de mail (file d'attente des mails),  je dois comparer 2 de ces fichiers (enregistrés avec quelques minutes de décalage) afin de relever les mails qui n'arrivent pas à partir.
J'ai eu le raisonnement suivant : Je lis le premier fichier, et, ligne par ligne je le compare à toutes les lignes du second fichier et j'envoie les lignes similaires dans un autre fichier appelé "same.txt". J'ai réalisé ce script :
 

Code :
  1. fichier1="/home/pedro/Bureau/Pierre/pq1.txt"
  2. fichier2="/home/pedro/Bureau/Pierre/pq2.txt"
  3. while read line < $fichier1
  4. do
  5.         read enil < $fichier2
  6.         if [ "$line" = "$enil" ]
  7.         then
  8.                 echo $line > same.txt
  9.         fi
  10. done


 
Cependant lorsque j'exécute ce code, seule la première ligne (qui est la même dans les deux fichiers) est affichée en boucle.
Pourriez-vous m'orienter ?


Message édité par black_lord le 04-02-2015 à 11:19:06
Reply

Marsh Posté le 04-02-2015 à 09:26:18   

Reply

Marsh Posté le 04-02-2015 à 11:18:24    

sinon tu as l'utilitaire "comm", qui est l'inverse de "diff"


---------------
uptime is for lousy system administrators what Viagra is for impotent people - mes unixeries - github me
Reply

Marsh Posté le 13-04-2015 à 15:40:41    

il te faut une deuxième boucle pour itérer toutes les lignes du second fichier.  
 
Pour l'instant tu itères sur toutes les lignes du premier fichier que tu compares a la première ligne du second fichier (second read).  
Donc seul la première ligne du premier fichier matche à priori.  
 
Tu as donc besoin d'une deuxième boucle while read do pour itérer les lignes de ton second fichier.  
 
 

Reply

Marsh Posté le 13-04-2015 à 22:03:50    

salut,
 
d'une part, le script original est faux : la boucle ne finira jamais, car read lit la première ligne du fichier.
le fichier doit être versé dans la boucle while :

Code :
  1. while read -r line; do echo "$line"; done <fichier


 
la suggestion de netmonk ne fonctionnera pas, parce que la deuxième boucle lira le flux dans lequel est versé le fichier.
il faut utiliser des descripteurs de fichier.
à condition que les fichiers ont le même nombre de lignes ceci devrait fonctionner (je te laisse faire la comparaison) :

Code :
  1. exec {desc1}<fichier1
  2. exec {desc2}<fichier2
  3. while read -ru $desc1 line1
  4. do
  5.    read -ru $desc2 line2
  6.    echo "$line1 ?= $line2"
  7. done

Reply

Marsh Posté le 14-04-2015 à 00:21:03    

La réponse de blacklord  n est pas satisfaisante ?

black_lord a écrit :

sinon tu as l'utilitaire "comm", qui est l'inverse de "diff"



---------------
Relax. Take a deep breath !
Reply

Marsh Posté le 14-04-2015 à 10:53:48    

OOPS

Citation :

la suggestion de netmonk ne fonctionnera pas, parce que la deuxième boucle lira le flux dans lequel est versé le fichier.

:sweat:
ce n'est pas ça non plus : j'ai confondu avec la lecture depuis stdin, qui ne fonctionne pas lors de la lecture d'un fichier.  :(
 
 
par contre, après test (de l'importance de tester les codes !!!), la suggestion de netmonk ne fonctionnera quand même pas, car pour chaque ligne du fichier #1, le fichier #2 sera lu entièrement.
 
vu qu'il est question de relever «les lignes similaires», comm est sans doute une meilleure alternative.

Reply

Marsh Posté le 14-04-2015 à 11:15:59    

o'gure a écrit :

La réponse de blacklord  n est pas satisfaisante ?


 
c'est trop efficace :o


---------------
uptime is for lousy system administrators what Viagra is for impotent people - mes unixeries - github me
Reply

Marsh Posté le 14-04-2015 à 12:09:10    

hum un algo basique et brute force consistant à trouver les lignes identiques dans deux fichiers je pense que deux boucles while do imbriquées qui comparent chaques lignes du premier fichier avec toutes les lignes du deuxieme fichier est plutot satisfaisant.  
 
Tu peux rajouter un break dans le while imbriqué pour sortir directement de celui-ci dés que tu trouves une ligne qui match.  
 
C'est juste que là j'ai pas la main sur une console, mais ce soir je sors un bout de code qui fonctionne.  
 
Cependant je suis d'accord pour utiliser comm commande native sera de facto beaucoup plus efficace.


Message édité par netmonk le 14-04-2015 à 13:34:28
Reply

Marsh Posté le 14-04-2015 à 14:37:32    

[0]$ cat test1
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
0 0
[0]$ cat test2
1 1
3 3
9 9
5 5
9 9
10 10
[0]$ cat comp
while read line1; do
        while read line2; do
                if [ "$line2" = "$line1" ]
                then
                        echo $line1
                        break
                fi
        done < test2
done < test1
[0]$ bash comp
1 1
3 3
5 5
9 9


Message édité par netmonk le 14-04-2015 à 14:58:07
Reply

Marsh Posté le 14-04-2015 à 18:24:47    

ça n'enlève rien à ce que j'ai dit.
si on retire le test (ou si on met un set -x), on voit tout de suite ce que je voulais vous faire éviter :

Code :
  1. $ cat test1
  2. 1 1
  3. 2 2
  4. 3 3
  5. 4 4
  6. 5 5
  7. 6 6
  8. 7 7
  9. 8 8
  10. 9 9
  11. 0 0
  12. $ cat test2
  13. 1 1
  14. 3 3
  15. 9 9
  16. 5 5
  17. 9 9
  18. 10 10
  19. $ while read line1; do while read line2; do echo "$line2" = "$line1"; done < test2; done < test1
  20. 1 1 = 1 1
  21. 3 3 = 1 1
  22. 9 9 = 1 1
  23. 5 5 = 1 1
  24. 9 9 = 1 1
  25. 10 10 = 1 1
  26. 1 1 = 2 2
  27. 3 3 = 2 2
  28. 9 9 = 2 2
  29. 5 5 = 2 2
  30. 9 9 = 2 2
  31. 10 10 = 2 2
  32. 1 1 = 3 3
  33. 3 3 = 3 3
  34. 9 9 = 3 3
  35. 5 5 = 3 3
  36. 9 9 = 3 3
  37. 10 10 = 3 3
  38. 1 1 = 4 4
  39. 3 3 = 4 4
  40. 9 9 = 4 4
  41. 5 5 = 4 4
  42. 9 9 = 4 4
  43. 10 10 = 4 4
  44. 1 1 = 5 5
  45. 3 3 = 5 5
  46. 9 9 = 5 5
  47. 5 5 = 5 5
  48. 9 9 = 5 5
  49. 10 10 = 5 5
  50. 1 1 = 6 6
  51. 3 3 = 6 6
  52. 9 9 = 6 6
  53. 5 5 = 6 6
  54. 9 9 = 6 6
  55. 10 10 = 6 6
  56. 1 1 = 7 7
  57. 3 3 = 7 7
  58. 9 9 = 7 7
  59. 5 5 = 7 7
  60. 9 9 = 7 7
  61. 10 10 = 7 7
  62. 1 1 = 8 8
  63. 3 3 = 8 8
  64. 9 9 = 8 8
  65. 5 5 = 8 8
  66. 9 9 = 8 8
  67. 10 10 = 8 8
  68. 1 1 = 9 9
  69. 3 3 = 9 9
  70. 9 9 = 9 9
  71. 5 5 = 9 9
  72. 9 9 = 9 9
  73. 10 10 = 9 9
  74. 1 1 = 0 0
  75. 3 3 = 0 0
  76. 9 9 = 0 0
  77. 5 5 = 0 0
  78. 9 9 = 0 0
  79. 10 10 = 0 0

ce n'est pas du tout satisfaisant.

Reply

Marsh Posté le 14-04-2015 à 18:24:47   

Reply

Marsh Posté le 14-04-2015 à 21:13:18    

Forcement tu fais une combinaison de toutes les lignes du premier fichier avec toutes les lignes du second fichier.  
 
Mon code est un peu plus optimisé puisque on sort du while imbriqué dès que l'on trouve un match.  
 
Cependant je ne comprend en rien tes histoires de descripteur de fichier. Tu as une page de manuel ou un lien vers de la doc ?

Reply

Marsh Posté le 14-04-2015 à 21:17:48    

sinon tu as l'utilitaire "comm", qui est l'inverse de "diff"


---------------
Décentralisons Internet-Bépo-Troll Bingo - "Pour adoucir le mélange, pressez trois quartiers d’orange !"
Reply

Marsh Posté le 14-04-2015 à 21:31:25    

Et d'un point de vue purement algorithmique je ne suis pas sûr que "comm" fasse vraiment mieux :)

Reply

Marsh Posté le 14-04-2015 à 21:56:16    

tu vas trouver des explications à propos des descripteurs de fichiers dans l'ABS.
quand une ligne d'un descripteur de fichier est lue, elle est retirée de ce dernier.
 
de manière POSIX, on ferait ainsi :

Code :
  1. while read -r line <&4
  2. do
  3.    read -r line2 <&5
  4.    echo "$line -- $line2"
  5. done 4<test1 5<test2
  6. 1 1 -- 1 1
  7. 2 2 -- 3 3
  8. 3 3 -- 9 9
  9. 4 4 -- 5 5
  10. 5 5 -- 9 9
  11. 6 6 -- 10 10
  12. 7 7 --
  13. 8 8 --
  14. 9 9 --
  15. 0 0 --

parce que POSIX read n'a pas d'option -u pour lire les df.
on pourrait aussi passer les fichier dans un df en utilisant exec.
 
il y a quand même plus de chances que comm fonctionne de cette façon.
les langages un peu plus bas niveau que le shell, comme perl, python, php, ouvrent un fichier dans un descripteur de fichier, puis parcourent ce descripteur de fichier.


Message édité par Profil supprimé le 14-04-2015 à 22:03:24
Reply

Marsh Posté le 14-04-2015 à 22:51:08    

Watael : dernière question: en quoi ton bout de code fait ce que demande le post initial ?  
C'est bien beau de m'empapaouter avec tes filesdesc mais moi je vois que ton résultat ne correspond en rien au problème initiale.
 
Merci donc de fournir un code qui réponde au problème initial et qui de surcroît spécifie à tes remarques.


Message édité par netmonk le 14-04-2015 à 22:52:45
Reply

Marsh Posté le 14-04-2015 à 23:08:02    

le problème de barbanegra était de lire deux fichiers dans une seul boucle while :

Citation :

Cependant lorsque j'exécute ce code, seule la première ligne (qui est la même dans les deux fichiers) est affichée en boucle.
Pourriez-vous m'orienter ?

s'il veut faire ça en shell, la meilleure solution est d'utiliser des df, et pas d'ouvrir autant de fois un fichier qu'il y a de lignes dans l'autre !

Reply

Marsh Posté le 14-04-2015 à 23:14:37    


 
Tu me trolles méchamment là l'ami, je cite :  

Citation :

J'ai 2 fichiers "queue -p" d'un serveur de mail (file d'attente des mails),  je dois comparer 2 de ces fichiers (enregistrés avec quelques minutes de décalage) afin de relever les mails qui n'arrivent pas à partir.  
J'ai eu le raisonnement suivant : Je lis le premier fichier, et, ligne par ligne je le compare à toutes les lignes du second fichier et j'envoie les lignes similaires dans un autre fichier appelé "same.txt". J'ai réalisé ce script :


 
Voilà! ce n'est pas parce que barbanegra n'a pas réussi a coder ce qu'il voulait réellement faire (incompréhension de l'utilisation de read) que son besoin n'est pas exprimé clairement.
Et c'est d'ailleurs ce que je dis dans mon premier post: son code ne correspond en rien à ce qu'il cherche à faire : identifier toutes les lignes du fichier 1 qui sont présentes dans le fichier 2 (et réciproquement) ou de manière plus logique faire une intersection du fichier1 et du fichier2.  
 
Et ni ton code ni le sien ne répondent à ce problème.  
 
Maintenant je ne suis pas dans le cerveau de barbe-noire, donc il existe une marge d'erreur.
 
Enfin j'ai utilisé tes fameux FD :

while read -r line1 <&9 ; do  
        while read -r line2 <&10; do  
                if [ "$line2" = "$line1" ]  
                then  
                        echo $line1  
                        break  
                fi  
        done 10< test2  
done 9< test1  


Ce qui me donne exactement la même sortie de bash -x que sans les redirections FD.  


Message édité par netmonk le 14-04-2015 à 23:18:10
Reply

Marsh Posté le 14-04-2015 à 23:21:20    

Sinon il y a grep aussi :  

plonky@netmonk ~ $ grep  -f test1 test2 |sort|uniq
1 1  
3 3  
5 5  
9 9


 
comm n'a pas l'air efficace :

plonky@netmonk ~ $ comm -12 test1 test2
1 1  
3 3  
9 9  
comm: le fichier 1 n'est pas dans l'ordre attendu
comm: le fichier 2 n'est pas dans l'ordre attendu


 
Et un petit bug dans comm :  

plonky@netmonk ~ $ comm -12 --nocheck-order test1 test2
1 1  
3 3  
9 9  


Il n'affiche pas "5 5" qui est commun aux deux fichiers.  


Message édité par netmonk le 14-04-2015 à 23:26:01
Reply

Marsh Posté le 14-04-2015 à 23:38:33    

'The infamous "Advanced" Bash Scripting Guide should be avoided unless you know how to filter out the junk. It will teach you to write bugs, not scripts. In that light, the BashGuide was written: http://mywiki.wooledge.org/BashGuide'
 
#bash@freenode

Reply

Marsh Posté le 14-04-2015 à 23:47:49    

Citation :

Ce qui me donne exactement la même sortie de bash -x que sans les redirections FD.  

normal, puisque tu génères un fd pour chaque ligne du premier fichier; les fd sont utiles parce qu'ils sont lus une seule fois.
 
comm ne convient pas non plus, alors, puisqu'il lit les deux fichiers ligne par ligne (ligne1.fichier1 ligne1.fichier2, etc).

Citation :

Forcing `comm' to process wrongly sorted input files containing
unpairable lines by specifying `--nocheck-order' is not guaranteed to
produce any particular output.  The output will probably not correspond
with whatever you hoped it would be.

3 3 ne devrait pas apparaître puisqu'ils ne sont pas sur la même ligne. :(
 
je plussoie la solution grep, pour faire ce que tu as compris.

Reply

Marsh Posté le 14-04-2015 à 23:53:58    

Pour enterrer la hache de guerre : http://mywiki.wooledge.org/BashFAQ/036
 
J'avais oublié le coup du uniq -d :

plonky@netmonk ~ $ sort test1 test2|uniq -d  
1 1  
3 3  
5 5  
9 9  


Message édité par netmonk le 14-04-2015 à 23:56:25
Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

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