IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Shell et commandes GNU Discussion :

[bash] Comparer deux fichiers csv


Sujet :

Shell et commandes GNU

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Invité
    Invité(e)
    Par défaut [bash] Comparer deux fichiers csv
    Bonjour à tous,

    Comme indiqué dans le titre, j'essaye de comparer deux fichiers CSV, malheureusement, sur un des fichiers, il y a plusieurs colonnes et je ne veux comparer qu'une des colonnes. Pour être plus claire, voici les fichiers:

    mac_port.csv
    00.1F.FF.5H.66 ; 23 (le ";" étant le délimiteur)
    00.1F.FF.4J.1B ; 14
    port.csv
    23
    je voudrais comparer la deuxième colonne du premier fichier avec le deuxième fichier, et dès qu'il y a concordance, je récupère en sortie la première colonne. Malheureusement, j'y étais arrivé en PowerShell, et Bash ne me simplifie pas la tâche. J'ai tenté avec cmp, diff, mais rien qui ne me permet de faire cela.

    Any ideas ?

  2. #2
    Modérateur
    Avatar de N_BaH
    Profil pro
    Inscrit en
    Février 2008
    Messages
    7 651
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 7 651
    Par défaut
    Bonjour,

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    awk -F' ; ' 'FNR=NR{a[$2]=$1;};{if($1 in a)print a[$1]}' mac_port port
    00.1F.FF.5H.66
    N'oubliez pas de consulter les cours shell, la FAQ, et les pages man.

  3. #3
    Membre Expert
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    792
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2004
    Messages : 792
    Par défaut
    Citation Envoyé par N_BaH Voir le message
    Bonjour,

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    awk -F' ; ' 'FNR=NR{a[$2]=$1;};{if($1 in a)print a[$1]}' mac_port port
    00.1F.FF.5H.66
    Je dirais plutôt:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    awk -F' ; ' 'FNR==NR{a[$2]=$1;next}a[$1]{print a[$1]}' mac_port port
    FNR=NR est toujours vrai. Il manque aussi, me semble-t-il, l'instruction next non?

  4. #4
    Rédacteur

    Avatar de ok.Idriss
    Homme Profil pro
    IS Consultant
    Inscrit en
    Février 2009
    Messages
    5 220
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : IS Consultant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2009
    Messages : 5 220
    Par défaut
    Bonjour.

    Possible aussi avec join :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [idriss@hp-dv6:~]$ cat f1.txt 
    23 ; dsdjksjfk
    32 ; sdosf
    56 ; sdsldk
    23 ; sfsofksofo
    [idriss@hp-dv6:~]$ cat f2.txt 
    00.1F.FF.5H.66 ; 23
    00.1F.FF.4J.1B ; 14 
    [idriss@hp-dv6:~]$ join -t';' -11 -22 <(sed 's/\ //g' f1.txt|sort) <(sed 's/\ //g' f2.txt|sort)|cut -d ';' -f3|sort -u
    00.1F.FF.5H.66
    Tu peux aussi bricoler avec grep :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    [idriss@hp-dv6:~]$ grep -f <(sed 's/\ //g' f1.txt|sort|cut -d';' -f1) <(sed 's/\ //g' f2.txt|sort)|cut -d ';' -f1
    00.1F.FF.5H.66
    Mais bon, la solution de N_Bah reste plus jolie

    Idriss

  5. #5
    Modérateur
    Avatar de N_BaH
    Profil pro
    Inscrit en
    Février 2008
    Messages
    7 651
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 7 651
    Par défaut
    j'allais proposer une solution avec join sur la base des fichiers fournis par El_Cypriano
    Code BASH : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $ join -t';' -o'1.1' -1 2 -2 1 <(sort -t';' -k2 <(sed 's/ *//g' mac_port)) port
    00.1F.FF.5H.66
    N'oubliez pas de consulter les cours shell, la FAQ, et les pages man.

  6. #6
    Invité
    Invité(e)
    Par défaut
    Merci N_BaH et ok.Idriss,

    Les solutions sont bonnes, mais quel est celui qui bouffe un peu de process, parce que ce n'est que le début d'un script long... trop long. Et donc, on m'a dit que "awk" était plus gourmand qu'autre chose, donc join ou grep permettrait d'être plus réactif !?

    Est-ce que vous être d'accord sur cela ?

  7. #7
    Expert confirmé Avatar de disedorgue
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Décembre 2012
    Messages
    4 347
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Décembre 2012
    Messages : 4 347
    Par défaut
    Citation Envoyé par El_Cypriano Voir le message
    Merci N_BaH et ok.Idriss,

    Les solutions sont bonnes, mais quel est celui qui bouffe un peu de process, parce que ce n'est que le début d'un script long... trop long. Et donc, on m'a dit que "awk" était plus gourmand qu'autre chose, donc join ou grep permettrait d'être plus réactif !?

    Est-ce que vous être d'accord sur cela ?
    Cela peut dépendre beaucoup de ton contexte, comme par exemple, quel sera la quantité de valeur dans ton 2éme fichier, juste une ???
    Ainsi que la quantité du 1er fichier ?
    Le but est de savoir si on doit s'arranger pour traiter tes fichiers en une seule passe (ici c'est le cas pour toutes les méthodes présentées car on a q'une seule donner à traiter dans le 2éme fichier sinon, plusieurs solutions ne sont pas bonnes du tout comme par exemple celle que j'ai proposée avec le xargs et le sed) et le awk peut être avantageux dans ce type de cas.

  8. #8
    Modérateur
    Avatar de N_BaH
    Profil pro
    Inscrit en
    Février 2008
    Messages
    7 651
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 7 651
    Par défaut
    Non. FNR et NR ne sont égaux que pour le premier fichier en entrée.
    le problème n'est pas là, mais dans mon oubli d'un second =
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    = #est une assignation, et donc toujours vrai
    == #est un opérateur de test
    N'oubliez pas de consulter les cours shell, la FAQ, et les pages man.

  9. #9
    Expert confirmé

    Profil pro
    Inscrit en
    Janvier 2011
    Messages
    1 946
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Janvier 2011
    Messages : 1 946
    Par défaut
    Salut,

    Citation Envoyé par N_BaH Voir le message
    j'allais proposer une solution avec join sur la base des fichiers fournis par El_Cypriano
    Code BASH : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $ join -t';' -o'1.1' -1 2 -2 1 <(sort -t';' -k2 <(sed 's/ *//g' mac_port)) port
    00.1F.FF.5H.66
    Et si on considère qu'il y a toujours des espaces avant et après le délimiteur qui est le point virgule, et donc qu'on prenne le délimiteur par défaut (l'espace), on peut faire ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $ join  -1 3 -2 1 -o 1.1 f1 f2
    00.1F.FF.5H.66

  10. #10
    Modérateur
    Avatar de N_BaH
    Profil pro
    Inscrit en
    Février 2008
    Messages
    7 651
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2008
    Messages : 7 651
    Par défaut
    vu qu'il faudra, en plus du join, trier au moins un fichier, plus peut-être un ou deux sed (ça dépend du format des fichiers), je pense que awk sera moins gourmand en ressources; d'autant que peut-être tout le script pourrait être écrit en awk...
    N'oubliez pas de consulter les cours shell, la FAQ, et les pages man.

  11. #11
    Rédacteur

    Avatar de ok.Idriss
    Homme Profil pro
    IS Consultant
    Inscrit en
    Février 2009
    Messages
    5 220
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : IS Consultant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2009
    Messages : 5 220
    Par défaut
    Bonsoir.

    Suffit de faire des tests de perf

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    [idriss@hp-dv6:~]$ content=""; for i in {1..1000}; do content=$(cat f1.txt <(echo -e "$content")); done;
    [idriss@hp-dv6:~]$ content2=""; for i in {1..1000}; do content2=$(cat f2.txt <(echo -e "$content2")); done;
    Pour ma version de join :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    [idriss@hp-dv6:~]$ date +%H:%M:%S:%N;join -t';' -11 -22 <(echo -e"$content"|sed 's/\ //g'|sort) <(echo -e "$content2"|sed 's/\ //g'|sort)|cut -d ';' -f3|sort -u;date +%H:%M:%S:%N
    17:46:56:957289092
    00.1F.FF.5H.66
    17:46:58:287069050
    Pour ma solution avec grep :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    [idriss@hp-dv6:~]$ date +%H:%M:%S:%N;grep -f <(echo -e "$content"|sed 's/\ //g'|sort|cut -d';' -f1) <(echo -e "$content2"|sed 's/\ //g'|sort)|cut -d ';' -f1|sort -u;date +%H:%M:%S:%N
    17:49:11:036894800
    00.1F.FF.5H.66
    17:49:11:148334877
    C'est rapide grep, en fait

    Et une solution avec awk inspirée de la solution de N_Bah mais permettant d'éviter les doublons sans passer par sort :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    awk -F' ; ' 'FNR=NR{a[$2]=$1;};{if($1 in a)t[a[$1]]=a[$1]} END {for (i in t)print t[i]}' f2.txt f1.txt
    Voici pour le test de perf :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    [idriss@hp-dv6:~]$ date +%H:%M:%S:%N;awk -F' ; ' 'FNR=NR{a[$2]=$1;};{if($1 in a)t[a[$1]]=a[$1]} END {for (i in t)print t[i]}' <(echo -e "$content2") <(echo -e "$content");date +%H:%M:%S:%N
    17:55:11:264311606
    00.1F.FF.5H.66
    17:55:11:289953146
    => Quasi instantané également.

    Pour départager grep et awk, on pourrait faire un test de perf à plus grande échelle (10 000 fois le contenu par exemple) mais bon le CPU de mon petit laptop morfle trop

    Comme dit N_Bah, la solution awk à l'avantage de n'utiliser qu'un processus donc préférable et est probablement plus performante à plus grande échelle.

    Idriss

  12. #12
    Expert confirmé Avatar de Flodelarab
    Homme Profil pro
    Inscrit en
    Septembre 2005
    Messages
    5 287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente (Poitou Charente)

    Informations forums :
    Inscription : Septembre 2005
    Messages : 5 287
    Par défaut
    Comme chacun y va de sa solution, j'y vais aussi:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    $ cat initial.txt 
    00.1F.FF.5H.66 ; 23
    00.1F.FF.4J.1B ; 14 
    $ cat filtre.txt 
    23
    $ sed 's/.*/;.*&/' filtre.txt|grep -f - initial.txt |sed 's/;.*$//'
    00.1F.FF.5H.66 
    $

  13. #13
    Expert confirmé Avatar de Flodelarab
    Homme Profil pro
    Inscrit en
    Septembre 2005
    Messages
    5 287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente (Poitou Charente)

    Informations forums :
    Inscription : Septembre 2005
    Messages : 5 287
    Par défaut
    Non. FNR et NR ne sont égaux que pour le premier fichier en entrée. Pour le second, NR retombe à 0 et FNR poursuit sa route.

    C'est pour cela que next est inutile et que la seconde accolade n'a pas besoin de condition. En effet, l'instruction "for i in ..." s'applique aux indices et pas aux valeurs. Aucun indice du tableau n'est un port puisque ce sont les adresses.

    Par contre, j'aurais mis "FNR==NR" et pas "="

  14. #14
    Membre Expert
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    792
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2004
    Messages : 792
    Par défaut
    Citation Envoyé par Flodelarab Voir le message
    Non. FNR et NR ne sont égaux que pour le premier fichier en entrée. Pour le second, NR retombe à 0 et FNR poursuit sa route.

    C'est pour cela que next est inutile et que la seconde accolade n'a pas besoin de condition. En effet, l'instruction "for i in ..." s'applique aux indices et pas aux valeurs. Aucun indice du tableau n'est un port puisque ce sont les adresses.


    Les indices sont précisément les numéros de port.($2 deuxième champ du premier fichier lu par awk).

    La condition de la deuxième instruction est indispensable. C'est elle qui permet d'imprimer les adresses (valeur du tableau a) ayant un numéro de port comme indice. La condition proposée par N_BaH était correcte. Elle pouvait aussi simplement s'écrire a[$1] qui s'évalue à TRUE si cet indice (port) existe dans le premier fichier.

    Enfin, l'instruction next est également indispensable pour "boucler" sur la première instruction et sa condition FNR==NR et d'ainsi rester sur le premier fichier. Sans elle, la deuxième instruction serait inutilement exécutée pour toutes les lignes du premier fichier. C'est la raison pour laquelle ok.Idriss a dû supprimer les doublons dans son test plus haut.

    Quant à la rapidité d'exécution, tout dépend du contexte et de la taille des fichiers. Y-a-t-il plusieurs lignes dans le fichier port? Risque t'on de retrouver des numéros de port dans les adresses mac (grep inutilisable)?

  15. #15
    Expert confirmé Avatar de disedorgue
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Décembre 2012
    Messages
    4 347
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Décembre 2012
    Messages : 4 347
    Par défaut
    Pourquoi grep serait-il inutilisable ???
    Rien n'empêche de réécrire à la volée le fichier de port pour créer le bon pattern et de dire au grep de prendre comme fichier de pattern, l'entrée standard (voir l'une des solution de flodelarab)

  16. #16
    Membre Expert
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    792
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2004
    Messages : 792
    Par défaut
    Citation Envoyé par disedorgue Voir le message
    Pourquoi grep serait-il inutilisable ???
    Rien n'empêche de réécrire à la volée le fichier de port pour créer le bon pattern et de dire au grep de prendre comme fichier de pattern, l'entrée standard (voir l'une des solution de flodelarab)
    Certes mais à quel coût? Combien de pipe ou sous-process? Ça me rappelle une réponse un brin humoristique d'un modérateur du forum unix.com
    Also, whenever you're doing grep | grep | awk | kitchen | sink, you should just replace it all with one simple awk command.

  17. #17
    Expert confirmé Avatar de disedorgue
    Homme Profil pro
    Ingénieur intégration
    Inscrit en
    Décembre 2012
    Messages
    4 347
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur intégration
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Décembre 2012
    Messages : 4 347
    Par défaut
    On est d'accord sur le fond, ici, on aurait:
    sed | grep | cut
    Techniquement, on ne parserait qu'une seule fois le fichier mac_port.
    Pour le fichier port, on le parse au moins une fois pour la mise en forme. puis formatage de la sorti par cut. Bizarrement, un grep+cut est aussi rapide qu'un sed qui fait la même chose (tout au moins sur des fichiers de mille lignes environ).

    En awk, on est a peu près équivalent, car il est obligé de lire au moins un fois chaque fichier, il y a donc au moins une mise en forme (donnée de parsing) et les données à parser + la mise en forme pour la sortie.

    Après, selon la regex, il n'est pas impossible que la méthode par le grep puisse prendre la main...

  18. #18
    Expert confirmé Avatar de Flodelarab
    Homme Profil pro
    Inscrit en
    Septembre 2005
    Messages
    5 287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente (Poitou Charente)

    Informations forums :
    Inscription : Septembre 2005
    Messages : 5 287
    Par défaut
    Citation Envoyé par ripat Voir le message
    Les indices sont précisément les numéros de port.($2 deuxième champ du premier fichier lu par awk).
    Levons l'ambiguïté: quand on se trouve dans la deuxième accolade pour le premier fichier, a[$1] utilise pour indice les adresses (00.1F.FF.5H.66) et n'a aucune chance de correspondre.
    Citation Envoyé par ripat Voir le message
    La condition de la deuxième instruction est indispensable.
    Elle est inutile. Que la confrontation ait lieu dans l'accolade ou avant l'accolade ne m'empêche pas de dormir.
    Citation Envoyé par ripat Voir le message
    La condition proposée par N_BaH était correcte. Elle pouvait aussi simplement s'écrire a[$1] qui s'évalue à TRUE si cet indice (port) existe dans le premier fichier.
    Elle est incorrecte car elle est toujours vraie alors que a[$1] n'est pas toujours vraie. Tu le dis toi-même
    Citation Envoyé par ripat Voir le message
    Sans elle, la deuxième instruction serait inutilement exécutée pour toutes les lignes du premier fichier.
    D'accord.
    Citation Envoyé par ripat Voir le message
    Quant à la rapidité d'exécution, tout dépend du contexte et de la taille des fichiers.
    On ne peut parler que de la vitesse algorithmique....
    Si tu commences à comparer le nombre de lignes, les microprocesseurs, etc, on n'est pas arrivé.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Also, whenever you're doing grep | grep | awk | kitchen | sink, you should just replace it all with one simple awk command.
    awk sait tout faire mais il le fait mal. grep ne sait que filtrer mais il le fait bien.
    Cf les temps que j'ai indiqué plus haut.


    Toutes ces choses étant dites, la méthode awk donnée ici est mauvaise car elle présuppose qu'il n'y a qu'une adresse par port. Ce qui paraît douteux. (L'inverse serait tout aussi douteux)

  19. #19
    Expert confirmé Avatar de Flodelarab
    Homme Profil pro
    Inscrit en
    Septembre 2005
    Messages
    5 287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente (Poitou Charente)

    Informations forums :
    Inscription : Septembre 2005
    Messages : 5 287
    Par défaut
    Non seulement il ne marche pas car l'association est douteuse, mais en plus, il y a un espace entre le ";" et le port qui fait que les deux ne correspondront jamais. Il faut proposer une solution du style:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $ awk -F';' '(FNR==NR){plouf=" "$1;a[plouf]++} (FNR>NR){if ($2 in a) print $1}' filtre.txt initial.txt
    00.1F.FF.5H.66
    ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $ awk -F'; ' '(FNR==NR){a[$1]++} (FNR>NR){if ($2 in a) print $1}' filtre.txt initial.txt
    00.1F.FF.5H.66

  20. #20
    Membre Expert
    Profil pro
    Inscrit en
    Mai 2004
    Messages
    792
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2004
    Messages : 792
    Par défaut
    Citation Envoyé par Flodelarab Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $ awk -F'; ' '(FNR==NR){a[$1]++} (FNR>NR){if ($2 in a) print $1}' filtre.txt initial.txt
    00.1F.FF.5H.66
    Tu pourrais te passer de la condition de la deuxième instruction en utilisant l'instruction next dans la lecture du premier fichier. De plus cette condition n'est pas correcte. FNR>NR s'évaluera toujours à FALSE. FNR ne sera jamais supérieur à NR dans la lecture de fichiers multiples. Impossible.

    La construction:awk 'FNR==NR{instruction pour fichier1; next} {instruction pour fichier2}' fichier1 fichier2 est un grand classique de lecture de fichier multiple.

    Un peu de lecture:
    http://www.unix.com/shell-programmin...oduce-3rd.html
    http://www.staff.science.uu.nl/~oost...k/nawk_77.html
    http://www.catonmat.net/blog/ten-awk...k_be_idiomatic

Discussions similaires

  1. [CSV] Comparer deux fichiers CSV
    Par Ghirahim dans le forum EDI, CMS, Outils, Scripts et API
    Réponses: 10
    Dernier message: 03/06/2015, 16h23
  2. Comparer deux fichiers csv
    Par kerplouz dans le forum Général Python
    Réponses: 1
    Dernier message: 21/11/2012, 21h17
  3. Comparer deux fichier
    Par Taz_8626 dans le forum Langage
    Réponses: 3
    Dernier message: 20/06/2006, 11h46
  4. comparer deux fichiers avec une api windows
    Par sweetdreamer dans le forum Windows
    Réponses: 4
    Dernier message: 25/05/2006, 22h10
  5. Fonction c qui compare deux fichiers ???
    Par babyface dans le forum C
    Réponses: 4
    Dernier message: 19/11/2005, 13h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo