Forum Programmation.shell Comment récupérer des valeurs dans un fichier pour effectuer une substitution dans un autre ? RESOLU

Posté par  .
Étiquettes :
0
8
nov.
2011

Bonjour,

J'ai besoin d'urgence de substituer dans un fichier des valeurs par d'autres présentes dans un autre fichier et je ne sait pas comment procéder.

N.B. Les 2 fichiers ont le même nombre de lignes (plusieurs centaines) et le champs1 (ville dans l'exemple) est dans le bon ordre dans le fichier2. Par contre il y a des trous dans la numérotation du fichier 1... Je précise aussi que je ne suis pas en bash 4 mais en 2.05b.0(1)-release

Exemple du fichier1 (contenant les infos à substituer) :

ville1 tata ville4 tbtb
ville3 ville5 tctc tdtd
ville4 tete ville1 tftf
ville5 tgtg thth ville3

Exemple du fichier2 (contenant les noms des villes) :

Lille
Paris
Lyon
Toulouse

Il me faut donc au final un fichier3 contenant :

Lille tata Lyon tbtb
Paris Toulouse tctc tdtd
Lyon tete Lille tftf
Toulouse tgtg thth Paris

Merci d'avance pour votre aide !

  • # join ?

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

    Tout est dans le titre.

    Système - Réseau - Sécurité Open Source - Ouvert à de nouvelles opportunités

    • [^] # Re: join ?

      Posté par  . Évalué à 0.

      Peut être... je regarde le man de join et je tente... merci.

      • [^] # Re: join ?

        Posté par  . Évalué à 0.

        J'essaye de comprendre le fonctionnement de la commande join car j'obtiens ceci quand je la tente même en demandant de joindre les 2 fichiers sur le champ 1 :

        join: fichier 2 n'est pas dans l'ordre attendu
        join: fichier 1 n'est pas dans l'ordre attendu

        Est-tu sur que cette commande est adaptée à ce que je souhaite faire ? Sachant que les 2 fichier n'ont pas de champs commun...

    • [^] # Re: join ?

      Posté par  . Évalué à 0.

      A présent j'en suis sur. Je ne peux pas faire cela avec join car il faut un champs commun dans les deux fichiers ce qui n'est pas mon cas...

    • [^] # Re: join ?

      Posté par  . Évalué à 0.

      De plus j'avais oublié que le champs1 pouvait se retrouver ailleurs dans le fichier1... désolé. Je mets à jour le post.

    • [^] # Re: join ?

      Posté par  . Évalué à 1.

      Grâce à toi, je lui ai trouvé une solution que j’espère il prendra le temps de comprendre.

      Je suis content, je viens de découvrir join. C’est une bonne journée, j’ai appris un truc.
      Par contre, je suis toujours pas au point avec cut, du coup, j’utilise awk :-p

      $ ./test.sh
      Lille tata Lyon tbtb
      Paris Toulouse tctc tdtd
      Lyon tete Lille tftf
      Toulouse tgtg thth Paris
      $ cat test.sh
      #!/bin/bash
      
      sed 's% %\n%g' fic1.txt | grep -E "ville[[:digit:]]+" | sort -u | awk '{num += 1; print num " " $1;}' >ville.txt
      
      awk '{num += 1; print num " " $1;}' fic2.txt >nom.txt
      
      join ville.txt nom.txt | awk '{print $2 " " $3}' >ville_nom.txt
      
       cp fic1.txt fic1_tmp.txt
      
       while read line; do set $line; sed -i 's%'$1'%'$2'%g' fic1_tmp.txt; done < ville_nom.txt
      
      cat fic1_tmp.txt
      rm fic1_tmp.txt nom.txt ville_nom.txt ville.txt
      $ 
      
      

      En moins de 20 mns…

  • # explications ?

    Posté par  . Évalué à 1.

    est ce que je comprend bien en disant que Adresse 1 correspond à la première ligne du fichier 2, adresse2 à la deuxième ...

    Est ce qu'il y a des trous, par exemple pas d'adressse3 dans le premier fichier ?

    Les fichiers sont ils gros ?

    Solution proposé : charger en mémoire dans le variables adresse1,adresse2, adressen, les différentes lignes du fichier 2.
    ensuite, boucle readline sur le premier fichier avec incrémentation d'un compteur de ligne, et echo de adresse${n} (utiliser expr) et de la suite de la ligne du premier fichier (on omet le premier champ).

    S'il y a des trous, il faut rajouter des tests pour vérifier que l'on est bien en phase.

    • [^] # Re: explications ?

      Posté par  . Évalué à 0.

      Tout a fait. Entre temps j'ai changé adresse pas ville dans mon exemple mais c'est bien ça ville1 correspond bien à la première ligne du fichier2, adresse2 à le deuxième...

      Non aucun trou.

      Les fichiers font plusieurs centaines de lignes.

      Trop de ligne pour gérer chaque ligne dans une variable je pense.

      Je pense qu'il faut charger la première ligne du fichier2 (Paris), faire un sed avec substitution globale dans le fichier1 pour remplacer tout les ville1 par Paris, ensuite charger la seconde ligne du fichier2 et refaire un sed etc... mais j'ai du mal (je débute en scripting et j'ai du mal a imbriquer 2 boucles

  • # solution en bash

    Posté par  (site web personnel, Mastodon) . Évalué à 1.

    Voici une solution à ton problème en bash (4) :

    $ cat a
    ville1 tata ville2 tbtb
    ville2 ville3 tctc tdtd
    ville3 tete ville4 tftf
    Ville4 tgtg thth ville3
    
    $ ville=( "" Lille Paris Lyon Toulouse )
    
    $ count=$(grep -io "ville[0-9]\+" a | sort -n | awk 'END{gsub("[a-zA-Z]+", ""); print}')
    
    $ for ((i=1; i<count; i++)); do sed -i -r "s@(v|V)ille${i}@${ville[i]}@g" a; done
    
    

    On ne peut pas mettre d'array dans le string...

    • [^] # Re: solution en bash

      Posté par  . Évalué à 0.

      Merci beaucoup pour ta proposition de solution mais le fichier des villes contenant plusieurs centaines de lignes ça vas pas être faisable comme ça je pense malheureusement. Il faut absolument que je lise les 2 fichiers et que je remplace toutes les occurrences comme par exemple ville1 par Lille, ville2 par Paris etc...

      • [^] # Re: solution en bash

        Posté par  . Évalué à 2.

        pourquoi ça ne passerais pas ?

        il lit bien tous le fichier A (et compte le nombre de ligne, range les villes dans un tableau)

        puis lis le fichier 2 pour traiter ligne par ligne et remplacer ville1 par paris, ville2 par lyon...

        • [^] # Re: solution en bash

          Posté par  . Évalué à 0.

          J'ai du mal à comprendre ta premiere ligne :

          ville=( "" Lille Paris Lyon Toulouse )

          Ca met juste dans la variable les 4 premieres valeurs du fichier mais ça ne lis pas tous le fichier.

          • [^] # Re: solution en bash

            Posté par  . Évalué à 2.

            parce qu'il ne va pas te donner la réponse à ton exercice

            il faut que tu cherches un peu,
            que tu comprennes quelle commande fait quoi
            et que tu adaptes les idées qu'on te balance.

            ;)

  • # typo :°

    Posté par  (site web personnel, Mastodon) . Évalué à 0.

    Apparemment on peut pas éditer : il faut mettre dans la boucle for count+1 pour gérer Toulouse ;)

    On ne peut pas mettre d'array dans le string...

    • [^] # Re: typo :°

      Posté par  . Évalué à 0.

      Comme dit au dessus mes fichiers font plusieurs centaines de lignes... ça ne passera donc pas avec ta solution il me semble malheureusement.

  • # si si, c'est jouable :

    Posté par  (site web personnel, Mastodon) . Évalué à 0.

    Suffit de récupérer le deuxième fichier dans un tableau :

    $ cat v
    Lille
    Paris
    Lyon
    Toulouse
    
    $ ville=( "" $(<v) )
    
    

    On ne peut pas mettre d'array dans le string...

    • [^] # Re: si si, c'est jouable :

      Posté par  (site web personnel, Mastodon) . Évalué à -1.

      Ce qui donnes au final :

      $ cat a
      ville1 tata ville2 tbtb
      ville2 ville3 tctc tdtd
      ville3 tete ville4 tftf
      Ville4 tgtg thth ville3
      
      $ cat v
      Lille
      Paris
      Lyon
      Toulouse
      
      $ ville=( "" $(<v) )
      
      $ count=$(grep -io "ville[0-9]\+" a | sort -n | awk 'END{gsub("[a-zA-Z]+", ""); print}')
      
      $ for ((i=1; i<count+1; i++)); do sed -i -r "s@(v|V)ille${i}@${ville[i]}@g" a; done
      
      $ cat a
      Lille tata Paris tbtb
      Paris Lyon tctc tdtd
      Lyon tete Toulouse tftf
      Toulouse tgtg thth Lyon
      
      

      On ne peut pas mettre d'array dans le string...

      • [^] # Re: si si, c'est jouable :

        Posté par  (site web personnel, Mastodon) . Évalué à 1.

        Une autre solution :

        $ cat a
        ville1 tata ville2 tbtb
        ville2 ville3 tctc tdtd
        ville3 tete ville4 tftf
        Ville4 tgtg thth ville3
        
        $ cat v
        Lille
        Paris
        Lyon
        Toulouse
        
        $ sed -i 's:$: :g' a
        
        $ c=0; while read a; do ((c++)); sed -i -r "s@(v|V)ille${c} @$a @g" a; done < v
        
        $ cat a
        Lille tata Paris tbtb
        Paris Lyon tctc tdtd
        Lyon tete Toulouse tftf
        Toulouse tgtg thth Lyon
        
        

        On ne peut pas mettre d'array dans le string...

    • [^] # Re: si si, c'est jouable :

      Posté par  . Évalué à 0.

      #!/bin/bash
      TAILLE=`wc -l /tmp/fichier2| cut -d " " -f 1` 
      for i in `seq 1 $TAILLE` ; do
        ville=`sed -n -e ${i}p /tmp/fichier2` 
        sed -i -e "s/ville${i}/$ville/g" /tmp/fichier1 
      done
      
      
      • [^] # ouch

        Posté par  (site web personnel, Mastodon) . Évalué à 0.

        Erreurs :

        • tu gère pas la casse (ville et Ville)
        • ville1 va matcher ville10 ville110 (...) et ville1
        • seq en bash c'est moche, on peut utiliser plus proprement for ((i=1; i<taille; i++))

        On ne peut pas mettre d'array dans le string...

        • [^] # Re: ouch

          Posté par  . Évalué à 2.

          • casse -> Ah oui, j'avais pas vu le V majuscule en dernière ligne. Mais apparemment l'auteur du post s'y connait un minimum, il peut adapter l'expression rationnelle tout seul aux vrais fichiers qu'il aura à traiter.
          • ville110 -> on peut faire la boucle en sens inverse avec |tac après seq pour éviter le problème.
          • je sais pas si seq c'est moche, c'est ce que je connais et j'ai a pas passé plus de 2 minutes avant que chez-moi-ça-marche sur l'exemple fourni. Bon mettons 3 minutes pour quand j'ai dû lire le manuel de cut pour éviter awk "{ print $1 }" (qui marche aussi très bien, mais je me suis dit « y'aura bien quelqu'un pour trouver que c'est moche »).

          Je cherche pas à donner une solution parfaite, je donne une des directions possibles, qui n'est juste pas totalement peaufinée. Perso je n'ai pas compris grand chose aux autres solutions proposées, et je vois pas pourquoi on serait obligé de répondre avec des tableaux bidimensionnels, des while ou des for si on sait faire autrement. There's more than one way to do it.

          • [^] # Re: ouch

            Posté par  . Évalué à 3.

            ville110 -> on peut faire la boucle en sens inverse avec |tac après seq pour éviter le problème.

            ou simplement faire un seq $count 1 -1
            pour compter de $count jusqu'à 1 avec un pas de -1 (donc compter à l'envers

  • # sed quand même

    Posté par  . Évalué à 2.

    j'arrive un peu après la bataille, mais, en tenant compte des deux premières remarques de sputnick:

    # fabrication d'un fichier de regex pour sed
    i=0; while read line; do i=$(($i+1)); echo "s/\\<ville$i\\>/$line/ig"; done < ./fichier2  > regex
    # et voilà !
    sed -f ./regex ./fichier1 > fichier3
    
    
    • [^] # Re: sed quand même

      Posté par  . Évalué à 0.

      MErci mais en fait j'ai de trous dans le numérotation donc ça n e fonctionne pas encore... je modifie à nouveau mon post de départ. Désolé.

  • # Plus simple :

    Posté par  (site web personnel, Mastodon) . Évalué à 0.

    cp fichier1 new_fichier
    c=0; while read a; do ((c++)); sed -i "s@\<ville$c\>@$a@gi" new_fichier; done < fichier2
    
    

    On ne peut pas mettre d'array dans le string...

    • [^] # Re: Plus simple :

      Posté par  . Évalué à 0.

      Merci mais en fait j'ai des trous dans la numérotation ce qui fait qu'il y a un décalage à partir de la... je cherche un méthode pour vraiement récupérer les infos ligne par ligne. exemple si par exemple il n'y a pas de ville2: ville1 -> Lille ville3 -> Paris etc... si vous voyez comment faire je suis preneur.

      • [^] # Re: Plus simple :

        Posté par  . Évalué à 2.

        un bête
        (( c++ )
        until grep -sqw ville$c
        do
        ((c++))
        done

        permet de s'assurer de l'existence de la présence de la ville ;)

        Il ne faut pas décorner les boeufs avant d'avoir semé le vent

  • # petit cours d'algorithme (algorythmique)

    Posté par  . Évalué à 2.

    quand on fait de la programmation, on oublie souvent de réfléchir en français, et en décortiquant le problème étape par étape.

    les programmeurs habitués et rodés à l'exercice font les deux en meme temps, mais parfois cela ne fonctionne pas.

    et pour les débutants, autant faire les choses bien.

    Alors commence par réfléchir à ton problème en français :

    • j'ai un fichier A qui contient des données, présentées en ligne, chaque ligne contenant un motif (ville1 tâta ville5 titi)
    • j'ai un deuxième fichier (B) contenant des données, présentée en ligne, présentant la correspondance ville1 -> paris, ville2 -> reims ...

    • ce que je veux : remplacer dans le premier fichier, les mots ville1 par Paris, ville2 par Reims, etc (tel que définis dans le fichier B).

    La premiere chose qui vient à l'esprit simplement, c'est :
    1°) lire une ligne dans le fichier B,
    2°) pour chaque ligne sortir le motif cherché "ville1" et le motif de remplacement 'Paris'
    3°) effectuer le remplacement dans FichierA avec le motif recherché/remplacé définit en 2°)
    4°) recommencer en 1°) avec la ligne suivante

    ce qui se traduit en algorithme suivant :

    tant qu'il y a des lignes dans fichierB
    lire une ligne
    extraire le motif de recherche
    extraire le motif de remplacement
    faire le rechercher/remplacer dans fichierA
    passer à la ligne suivante

    en général une fois l'algorithme posé, l'écrire dans du code n'est plus qu'une histoire de traduction.

    en perl :

    while ($_)
    ...
    
    

    en bash

    readline
    ...
    
    
  • # ZSH encore vainqueur par KO!

    Posté par  . Évalué à 3.

    En 5 lignes, sans aucune commande externe:

    #!/bin/zsh
    v=( $(<$1) )
    d=( ${(f)"$(<$2)"} )
    for (( i=1; i <= ${#d}; i++ ))
    	d=( ${d//${${=d[i]}[1]}/$v[i]} )
    print -l $d
    
    

    Avant de passer aux explications, ouvrez votre fenêtre et criez « Gloire à Zsh » 5 fois à la cantonade.

    On part du principe que v et d font la même longueur, et que le premier mot de chaque ligne de d est unique.

    Dans le tableau v, on met le fichier en argument 1 contenant le nom des ville (1 élément = 1 ville).
    Dans le tableau d, on met le fichier en argument 2 avec les infos à substituer (1 élément = 1 ligne).
    Puis, pour chaque ligne n de d, on remplace le premier mot de cette ligne par la n-ième ville (avec le remplacement qui s'applique sur la totalité de d).
    Enfin on affiche d en sautant une ligne à chaque élément.

    Bien entendu, je viens de pondre ce script du premier coup, et je ne l'ai même pas vérifié tellement il est évident qu'il marche... Que je sois foudroyé si je m

  • # Résolu

    Posté par  . Évalué à 0.

    Bonjour,

    Le problème à été résolu de la manière suivante :

    i=1
    while read line
    do
    valeur=$(sed -n "${i}p" fichier2)
    sed -i "s/${line%% *}/${valeur}/Ig" fichier1
    ((i++))
    done < fichier1

    N.B. : La solution n'est pas de moi mais d'une personne d'un autre forum

    Merci à tous pour votre aide et vos conseils.

Suivre le flux des commentaires

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