Forum Programmation.shell Hachage d'un document .csv

Posté par .
Tags : aucun
2
14
fév.
2010

Bonjour,

Je souhaiterais importer dans une base de donnée une classification de villes.

Le fichier .csv est constitué ainsi et non modifiable:
france,"paris","E1"
france,"lyon","E2"
chine,"pekin","A1"
etc...

Je cherche à avoir la nomenclature suivante:

fraparis1
fralyon2
chipekin1
etc...

J'ai écris le script suivant:


#!/bin/sh

#Pour appeler le fichier.

cat villes.csv | while read ligne

do

#Pour extraire chaque élément et les mettre dans des variables différentes.
pays=`echo $ligne | tr -d \" | cut -d',' -f1 | cut -c1-3`
ville=`echo $ligne | tr -d \" | cut -d',' -f2`
num=`echo $ligne | tr -d '"' | cut -d',' -f3`

#Pour trier
tri=`echo $pays | cut -c1-3 && echo $ville && echo $num | tr -d \[A-Z]`
echo $tri

done


Qui me donne le résultat suivant:

fra paris 1
ita rome 1
chi pekin 1
fra lyon 2
per lima 1
esp madrid 1
esp barcelonne 2

Comment supprimer les espaces?

Amitiés à tous les linuxiens et OOoiens
  • # toujours tr

    Posté par . Évalué à 3.

    echo $tri | tr -d ' '
  • # IFS: Input field separator

    Posté par (page perso) . Évalué à 5.

    echo $tri | tr -d ' '

    Non, je plaisante, hein.

    info bash "Shell Variables" "Bourne Shell Variables"

    IFS permet de dire comment splitter une ligne de texte en lexèmes. Ça te permettra quelque chose de bien plus simple que ce que tu as ici :

    #!/bin/bash

    IFS=","

    cat villes.csv | tr -d \" | while read pays ville num; do
    pays=$(echo $pays | cut -c1-3)
    num=$(echo $num | tr -d '[A-Z]')

    echo "$pays$ville$num"
    done

    Je n'ai pas compris le commentaire "Pour trier", puisque tu ne tries rien du tout, donc j'ai peut-être loupé quelque chose.

    Quoi qu'il en soit : en modifiant IFS, je laisse au shell le soin de splitter la ligne lui-même. Ça évite les | cut -d, -f X. Ensuite, en factorisant la suppression des guillemets, il est plus simple de voir le traitement qui est commun à tous les champs et celui qui diffère à chaque fois. Tu tronquais deux fois $pays à trois caractères, j'ai supprimé ça. J'ai ramené la suppression des lettres de $num en-dehors de l'echo.

    Pour revenir à la question initiale, $tri contient après l'exécution de la longue ligne chaque champ, séparés par des retours à la ligne. Quand ton shell lit "echo $tri", avec la valeur par défaut de IFS, il sépare $tri en trois lexèmes, passés en arguments à echo, qui les affiche les uns après les autres séparés par des espaces.

    Questions en suspens : que se passe-t-il le jour où une ville contient une virgule, par exemple si tu fais du commerce avec "Paris, Texas" ? Est-on sûrs que le champ $num est toujours de la forme Xn, avec X une lettre et n un unique chiffre ? Que se passe-t-il le jour où quelqu'un ajoute un quatrième champ ? N'y a-t-il pas d'accents sur le nom « Perou » ou « Pekin » ? Pourquoi les habitants de Barcelonne, ce (probablement charmant) village de la Drôme ont-ils fait allégeance à la couronne espagnole ?
  • # les regex, c'est bien

    Posté par (page perso) . Évalué à 6.

    Là, comme ça, sans plus d'info, je remplacerais ton script par cette ligne :
    perl -ne 's/^(.{3}).*"(.*)",".(.)"$/$1$2$3/;print ' /tmp/plop.txt

    où /tmp/plop.txt est ton fichier.

    Je considère que tu n'auras pas d'autres guillemets que ceux qui entourent les 2° et 3° champs, et que le 3° champs sera toujours constitué de deux caractères...
  • # Hmmm

    Posté par (page perso) . Évalué à 3.

    Cette ligne est très bizarres :

    tri=`echo $pays | cut -c1-3 && echo $ville && echo $num | tr -d '[A-Z]'`

    Pourquoi cut -c1-3 vu que tu l'as fait avant ?
    Pourquoi des &&

    Tu pourrais par exemple faire :

    tri=$pays$ville`echo $num | tr -d '[A-Z]'`

    Ou mieux, faire le tr -d sur la ligne ou tu generes $num, comme pour les autres
  • # sed, c'est bien (je l'ai déjà faite, je sais)

    Posté par (page perso) . Évalué à 4.

    Bien plus simple, tout comme la proposition en Perl plus haut:
    sed 's/^(.{3}).*,"(.*)",".(.)"$/\1\2\3/' | sort
    Non testé, mais en gros c'est ça.
  • # en Bash

    Posté par . Évalué à 4.

    while IFS="," read pays ville num
    do echo "${pays::3}${ville//\"}${num:2:1}"
    done <nanard06.csv
    fraparis1
    fralyon2
    chipekin1
  • # En AWK

    Posté par . Évalué à 3.

    En presque AWK, tu peut faire ainsi :
    cat fichier | tr -d \" | awk -F, '{ print substr($1, 1, 3)$2substr($3, 2, length($3)-1) }'

    Sinon en pur AWK tu peut créer un fichier pars.awk :
    BEGIN{
    FS = ","
    }

    {
    sub(/"/, "", $2);
    sub(/"/, "", $2);
    sub(/"/, "", $3);
    sub(/"/, "", $3);
    print substr($1, 1, 3)$2substr($3, 2, length($3)-1)
    }

    Et t'en servir ainsi :
    awk -f pars.awk fichier

    Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: En AWK

      Posté par . Évalué à 2.

      UUOC detected
      • [^] # Re: En AWK

        Posté par . Évalué à 2.

        Je n'ai pas fais le test mais dans mon man de tr et j'ai vu qu'il ne peut pas prendre en paramettre de fichier. J'ai tellement peut l'habitude de l'opérateur < que je l'ai oublié. Merci de me faire la remarque.

        On doit plutôt, donc faire ceci :
        tr -d \" < fichier | awk -F, '{ print substr($1, 1, 3)$2substr($3, 2, length($3)-1) }

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

  • # Hachage d'un document .csv

    Posté par . Évalué à 2.

    Un grand merci à tous. VRAIMENT!

    J'ai utilisé IFS et ça fonctionne super (je ne connaissais pas ce truc).



    Problème résolu à 100%
  • # Résultat

    Posté par . Évalué à 3.

    La solution finale qui va bien.
    #!/bin/bash
    
    IFS=","
    
    cat villes.csv | tr -d \" | while read pays ville num; do
    pays=`echo $pays | cut -c1-3`
    ville=`echo $ville`
    num=`echo $num | tr -d '[A-Z]'`
    
    echo "$pays$ville$num"
    done
    
    
    Voilà le résultat.
    
    fraparis1
    itarome1
    chipekin1
    fralyon2
    perlima1
    espmadrid1
    espbarcelonne2
    • [^] # Re: Résultat

      Posté par . Évalué à 2.

      UUOC !!!
      • [^] # Re: Résultat

        Posté par . Évalué à 2.

        Comme plus haut ça c'est mieux :
        tr -d \" < villes.csv | while read pays ville num; do

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Résultat

      Posté par . Évalué à 2.

      Pourquoi avoir choisi cette solution ? Les solution à base d'expressions régulière se font en une seule ligne.
      sed -e 's/^\(.\{3\}\).*,"\(.*\)",".\(.\)"$/\1\2\3/' villes.csv

      (Oui il y a de l'antislash)

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Re: Résultat

        Posté par (page perso) . Évalué à 1.

        Bien que je sois un fervent partisan des expressions rationnelles, je peux comprendre que dans certains cas, on préfère plusieurs lignes de bash à une seule ligne avec une regex. Même si celles présentes sur cette page restent triviales, quelques lignes de bash restent plus compréhensibles si le script risque par la suite d'être maintenu par des néophytes...
  • # En python et en utilisant les libs faites pour

    Posté par . Évalué à 2.

    Le CSV a beau être un format un peu bâtard, il existe certaines "normes" qui sont implémentées par les librairies adéquates. En particulier, enlever les guillemets doubles à l'arrache, pourquoi pas, mais on sait jamais, si une donnée est formatée un peu bizarrement ...

    Bref, le code, en utilisant csv, en python :

    import csv
    for line in csv.reader(file("villes.csv")):
      print line[0] + line[1] + line[2]


    Encore plus lisible que les solutions précédentes.
    Et le mieux, c'est que comme tu es dans un vrai langage, tu pourras même utiliser les bindings SQL pour intégrer ça dans ta base ! Genre, après avoir récupéré un curseur "c", mettre dans la boucle :

    c.execute("INSERT INTO matable (pays, ville, num) VALUES ?, ?, ?", line)

    Voilà, je trouve ça encore plus efficace que les solutions précédentes.
    • [^] # Re: En python et en utilisant les libs faites pour

      Posté par . Évalué à 3.

      Les expressions régulière tel que présentées plus haut ne posent pas de problèmes avec les " n'importent où.

      Ensuite pour ce qui est de l'usage d'un « vrais langage » (j'imagine que ni sed, ni sh, ni awk sont de vrais langages), tu as perl qui peut très bien réussir cette tâche avec un machin du type :
      open(CSV, "villes.csv") ¦¦ die ("Erreur d'ouverture de villes.csv");

      while(){
      $_ =~ /^(.{3}),"(.*)",".(\d*)"$/;
      c->execute('INSERT INTO matable (pays, ville, num) VALUES '.$1.', '.$2.', '.$3);
      }


      Voili voilou

      Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Re: En python et en utilisant les libs faites pour

        Posté par . Évalué à 4.

        Les expressions régulière tel que présentées plus haut ne posent pas de problèmes avec les " n'importent où.

        Et si, dans le cas où on a un guillemet échappé dans la chaîne ... OK, tu vas me dire, "ça arrivera jamais, aucune ville n'a de guillemet dans son nom", mais "on sait jamais" ... (je ferais bien plus attention aux erreurs de traitement qui pourraient par exemple intégrer un guillemet inséré à cause d'un bug de double échappement ou un truc du genre)

        Ma "logique" c'était que tant qu'on a une lib qui suit la "norme" sans se prendre la tête avec les regexp, tout en ayant un langage qui permet d'aussi facilement l'utiliser, autant en profiter.

        Enfin, mes excuses pour la pique sur les "vrais langages", c'est vrai que j'ai vu des morceaux de sed et awk assez impressionnant sur ce forum.
        • [^] # Re: En python et en utilisant les libs faites pour

          Posté par . Évalué à 2.

          Et si, dans le cas où on a un guillemet échappé dans la chaîne ... OK, tu vas me dire, "ça arrivera jamais, aucune ville n'a de guillemet dans son nom", mais "on sait jamais" ... (je ferais bien plus attention aux erreurs de traitement qui pourraient par exemple intégrer un guillemet inséré à cause d'un bug de double échappement ou un truc du genre)
          J'avais bien compris et les expressions régulières gèrent parfaitement ces cas là.

          Ma "logique" c'était que tant qu'on a une lib qui suit la "norme" sans se prendre la tête avec les regexp, tout en ayant un langage qui permet d'aussi facilement l'utiliser, autant en profiter.
          1 000 excuses je ne savais pas du tout d'une part que le format a fait l'objet d'une RFC, d'autre part qu'il existait des bibliothèques pour les gérer.

          Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

Suivre le flux des commentaires

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