Forum Programmation.shell concaténer et dé-dupliquer fichiers

Posté par  . Licence CC By‑SA.
Étiquettes :
1
14
fév.
2017

Bonjour,

Le titre n'est peut-être pas très équivoque mais je vais essayer de vous expliquer au mieux mon problème.
J'ai un script qui récupère les valeurs de plusieurs fichiers et qui les ajoute les une à la suite des autres. Afin de conserver l'origine de la valeur, je rajoute en commentaire le nom du fichier source.

Ce qui me donne quelque chose comme ça :

$ cat test.txt
toto #TEST1
tata #TEST1
titi #TEST1
toto #TEST2
titi #TEST2
titi #TEST3

Afin d'exploiter au mieux ce fichier, j'ai besoin de supprimer les doublons. Cependant je souhaiterais conserver les commentaires et les concaténer. Le but est d'avoir à la fin un fichier qui ressemble à ça :

$ cat test.txt
toto #TEST1,TEST2
tata #TEST1
titi #TEST1,TEST2,TEST3

Voila je fais appel à vous car je ne trouve pas le moyen de faire ça… :(
Si quelqu'un à une idée ?

Merci !

  • # XY Problem ?

    Posté par  . Évalué à 3.

    Ça me fait penser à un XY problem parce que quand on a besoin de savoir d'où vienne les choses, il est raisonnable de ne pas commencer par les mélanger…
    C'est quoi le problème ? ^

    http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem

  • # 1 les classer, 2 les commenter

    Posté par  . Évalué à 4.

    1°) les classer
    sort -nk1 fichier_source

    ca va te donner un truc comme ca

    titi #test1
    titi #test2
    titi #test3
    toto #test2
    toto #test4

    2°) lire ces nouvelles données, ligne par ligne,
    et si le premier champs est le meme que la ligne precedente, alors concatener le champs commentaire courant avec celui de la ligne precedente,
    sinon demarrer un nouveau commentaire

    en shell ce pourrait etre

    while read valeur commentaire
    do
      if [[ $valeur == $ancienvaleur ]]
      then
        newcommentaire=$anciencommentaire","$commentaire
      else
        ## sortie ecran de la derniere valeur et de ses commentaires :
        echo "$ancienvaleur,$anciencommentaire"
        ## demarrons la nouvelle serie
        newcommentaire=$commentaire
      fi
      anciencommentaire=$newcommentaire
      ancienvaleur=$valeur
    done < <(sort -nk1 fichier_source)

    en veillant à la protection des variables, on doit arriver à ce que tu veux

    • [^] # Re: 1 les classer, 2 les commenter

      Posté par  (site web personnel) . Évalué à 2. Dernière modification le 14 février 2017 à 14:57.

      Quelques remarques:

      1. Attention à la protection des variables, tout explose si les variables ont plusieurs mots!

      [[ "${valeur}" == "${ancienvaleur}" ]]

      Et puis quitte à utiliser des mots français, autant accorder l'adjectif, non? :)

      1. echo n'est ni robuste ni portable et ne doit être utilisé que pour des messages fixes.

      printf '%s,%s\n' "${ancienvaleur}" "${anciencommentaire}"

      1. Ton programme a un petit problème d'IFS, il lit le # dans anciencommentaire.

      2. Il crée une ligne supplémentaire , au tout début!

      En traduisant le code en sh cela devient donc

          sort -nk1 fichier_source | while IFS='# ' read valeur commentaire
          do
            if [ "${valeur}" = "${ancienvaleur}" ]
            then
              newcommentaire="${anciencommentaire},${commentaire}"
            elif [ -n "${ancienvaleur}" ]
              printf '%s,%s\n' "${ancienvaleur}" "${anciencommentaire}"
              newcommentaire="${commentaire}"
            fi
            anciencommentaire="${newcommentaire}"
            ancienvaleur="${valeur}"
          done

      (Il y a un petit problème de mathjax dans le bloc de code avec tripe contr'apostrophe, désolé pour le formatage.)

      • [^] # Re: 1 les classer, 2 les commenter

        Posté par  . Évalué à 2.

        (Il y a un petit problème de mathjax dans le bloc de code avec tripe contr'apostrophe, désolé pour le formatage.)

        c'est parce qu'il faut preciser le langage apres les ```
        dans l'exemple en dessous (dans la FAQ tout en bas) ```python

        et pour une ligne simple, le ` simple est suffisant

        une ligne de code simple

        plusieurs
        ligne
        de
        code
      • [^] # Re: 1 les classer, 2 les commenter

        Posté par  . Évalué à 0.

        [[ "{valeur}" == "{ancienvaleur}" ]]

        Les accolades sont inutiles dans ce cas, ceci est suffisant :

        [[ "$valeur" == "$ancienvaleur" ]]
        end
        • [^] # Re: 1 les classer, 2 les commenter

          Posté par  (site web personnel) . Évalué à 3.

          Les accolades sont inutiles dans ce cas, ceci est suffisant :

          C'est exact, cependant lorsqu'on programme beaucoup en shell, on utilise souvent les modificateurs :?, :-, :+ etc. qui eux nécessitent les accolades, on prend alors l'habitude d'utiliser systématiquement les accolades.

  • # Éviter de faire des traitements en shell

    Posté par  (site web personnel) . Évalué à 3.

    Mon point de vue est que le shell est très bien pour combiner des traitements ensemble mais qu'il est plutôt à éviter pour implémenter ces traitements eux-mêmes. Les raisons sont que le langage rend la description de ces traitements difficile – même si certains dialectes comme bash ou zsh améliorent un peu la situation. Voici donc une procédure awk qui fait le travail, mais beaucoup d'autres langages peuvent être utilisés à sa place:

    BEGIN {
        FS=" #"
    }
    
    {
        if($1 in seen) {
            origin[$1] = origin[$1] "," $2
        } else {
            origin[$1] = $2
        }
        seen[$1]
    }
    
    END {
        for(result in seen){
            printf("%s #%s\n", result, origin[result]);
        }
    }

    Le cœur du programme est la partie

    {
        if($1 in seen) {
            origin[$1] = origin[$1] "," $2
        } else {
            origin[$1] = $2
        }
        seen[$1]
    }

    qui est appliquée sur toutes les lignes de l'entrée. Les variables $1 et $2 renvoient à la première et deuxième colonne de la ligne en cours de traitement, la déclaration BEGIN { FS=" #" } dit justement comment ces colonnes sont délimitées. La phrase seen[$1] est peut être un peu bizarre, elle correspond à l'ajout d'une clef sans valeur dans un tableau associatif.

    Lorsque toutes les lignes ont été traitées, awk* passe au bloc END dont le contenu est lui aussi explicite.

    • [^] # Re: Éviter de faire des traitements en shell

      Posté par  . Évalué à 4.

      Juste pour le fun, j'avais besoin de me changer les idées.
      En R avec le package dplyr, le script admet 2 paramètres, un nom de fichier d'entrée et un nom de fichier de sortie :

      #!/usr/bin/Rscript --slave
      argv <- commandArgs(TRUE)
      library(dplyr)
      
      dta<-read.table(argv[1],comment.char = "",sep="#")
      sortie<-dta %>% group_by(V1) %>% summarise(r=paste(V2,collapse=","))
      write.table(sortie,argv[2],row.names = F,col.names = F,sep="#",quote=F)

      Je sors

  • # presque

    Posté par  . Évalué à 1.

    Salut NeoX et merci !

    J'ai un peu adapté ton script et c'est presque bon :

    while read valeur commentaire
    do
      if [[ $valeur == $ancienvaleur ]]
      then
        newcommentaire=$anciencommentaire","$commentaire
        echo "$valeur $newcommentaire"
        else
        echo "$valeur $commentaire"
        newcommentaire=$commentaire
      fi
      anciencommentaire=$newcommentaire
      ancienvaleur=$valeur
    done < <(sort -nk1 test.tmp)

    Voici le résultat :

    tata #TEST1
    titi #TEST1
    titi #TEST1,#TEST2
    titi #TEST1,#TEST2,#TEST3
    toto #TEST1
    toto #TEST1,#TEST2

    Reste maintenant à supprimer les lignes 2,3 et 4.

    • [^] # Re: presque

      Posté par  . Évalué à 1.

      Une autre solution si tu utilises bash :

      #!/bin/bash
      declare -A o
      while read
      do
              k=$(echo $REPLY | cut -d# -f1)
              v=$(echo $REPLY | cut -d# -f2)
              [ ${o[$k]+_} ] && o[$k]=${o[$k]}', '$v || o[$k]=$v
      done < "$1"
      
      for k in "${!o[@]}"; do echo "$k # ${o[$k]}"; done

      Ca donne ça :

      $ ./script1 data.txt 
      titi  # TEST1, TEST2, TEST3
      tata  # TEST1
      toto  # TEST1, TEST2
  • # Bonjour

    Posté par  . Évalué à 5.

    Le titre n'est peut-être pas très équivoque mais je vais essayer de vous expliquer au mieux mon problème.

    Je pense que tu voulais dire que ton titre est équivoque au contraire.

    http://www.larousse.fr/dictionnaires/francais/%C3%A9quivoque/30718

    Le contraire de « équivoque » est, si je ne m’abuse, « univoque ».

  • # Perl ? (:

    Posté par  . Évalué à 2. Dernière modification le 16 février 2017 à 17:37.

    Un petit passage dans un hash (dont les clés sont uniques) permet d’éliminer des doublons.
    Le reste n’est que de la mise en forme.

    moi@myhome:/tmp$ cat {test.txt,quickperl.pl} ; perl quickperl.pl < test.txt 
    toto #TEST1
    tata #TEST1
    titi #TEST1
    toto #TEST2
    titi #TEST2
    titi #TEST3
    
    my %h; # Oh le beau hash !
    while (<>) { # Mange un fichier qu’on lui donne
      chomp; my @a = split(' #'); # Met en forme pour traitement
      $h{$a[0]} .= ",$a[1]"; # Élimine les doublons
    }
    
    foreach (keys %h) {
      $h{$_} =~s/^,//;
      print "$_ #$h{$_}\n";
    }
    
    
    titi #TEST1,TEST2,TEST3
    toto #TEST1,TEST2
    tata #TEST1

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.