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] Modifier la valeur du premier champ d'une ligne si elle existe déjà


Sujet :

Shell et commandes GNU

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 25
    Par défaut [bash] Modifier la valeur du premier champ d'une ligne si elle existe déjà
    Bonjour,

    J'ai un fichier CSV contenant des lignes commençant par un nombre, en voici un extrait simplifié :
    "5","Lorem","ipsum",
    "6","dolor","sit"
    "7","amet","consectetur"
    "8","adipiscing","elit."
    "9","Sed","non"
    "10","risus.","Suspendisse"
    "11","lectus","tortor,"
    "1","dignissim","sit"
    "10","amet,","adipiscing"
    "11","nec,","ultricies"
    "6","sed,","dolor."
    On voit que le premier champ contient un nombre. Mais les trois derniers sont déjà présents plus haut dans le fichier ; ce sont ceux-là que je voudrais transformer, en les incrémentants. Voici le résultat que je souhaiterais obtenir :
    "5","Lorem","ipsum",
    "6","dolor","sit"
    "7","amet","consectetur"
    "8","adipiscing","elit."
    "9","Sed","non"
    "10","risus.","Suspendisse"
    "11","lectus","tortor,"
    "1","dignissim","sit"
    "12","amet,","adipiscing"
    "13","nec,","ultricies"
    "14","sed,","dolor."
    Mais je sèche. Ça dépasse mes capacités en shell. Pour l'instant, j'arrive simplement à récupérer tous les premiers champs, mais je ne sais pas quoi en faire après :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    while read ligne; do
        echo $ligne | cut -f1 -d ","
    done < $fichier
    Un plus expert que moi pourrait-il trouver une solution ?

    Cordialement,
    Sémhur

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 5 308
    Par défaut
    Bonjour

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    awk 'BEGIN{FS=",";OFS=FS;} {$1=substr($1,2,length($1)-2);} (a[$1]>0){$1=max;} {a[$1]++;if ($1>=max) max=$1+1;} {$1="\""$1"\"";} 1'
    La deuxième et la cinquième accolades servent à enlever et remettre les doubles quotes, supposés toujours présents.
    Le tableau "a" stocke le nombre de fois qu'un motif est vu.
    "max" est le prochain indice libre à utiliser pour dédoublonner.

    Toute la numérotation peut être possiblement décalée.
    Mais il semble que ce soit ce que tu veux.

  3. #3
    Invité
    Invité(e)
    Par défaut
    c'est bizarre de faire une "renumérotation" à la volée
    et je ne vois pas où tu testes que l'indice est déjà utilisé :callim2:

    j'aurais attendu d'avoir lu tout le fichier pour connaître la valeur maximale de l'indice, et marquer les doublons, pour, dans l'action END, traiter le tableau et incrémenter les doublons*...

    *n'y a-t-il que des doublons, ne peut-il y avoir des "triplons", voire plus

  4. #4
    Expert confirmé Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    3 020
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 3 020
    Par défaut
    Si la première colonne n'est pas associée à quelque chose en particulier (comme le laisse présager le fait qu'il y a des doublons), pourquoi ne pas carrément la virer et la remplacer en utilisant NR:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    awk -F, -v OFS=, '{$1="\""NR"\""}1' file
    Y a-t-il une raison quelconque de conserver ces valeurs? S'agit il de corriger une ânerie commise à partir des premiers doublons, et dans ce cas le reste doit être gardé tel quel?

  5. #5
    Membre averti
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 25
    Par défaut
    Waouh.
    C'est là que je me dis que j'ai une grosse marge de progression en shell et ses outils...

    Il y a juste un problème que je ne m'explique pas : on perd la première ligne ?
    Pour contourner, j'en ajoute une vide au début, mais je suppose qu'il y a moyen de faire plus propre.

    Voici mon code maintenant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    sed -i "1i\\n" $fichier
    while read ligne; do
        awk 'BEGIN{FS=",";OFS=FS;} {$1=substr($1,2,length($1)-2);} (a[$1]>0){$1=max;} {a[$1]++;if ($1>=max) max=$1+1;} {$1="\""$1"\"";} 1' >> tempfile
    done < $fichier
    mv tempfile $fichier

  6. #6
    Membre averti
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 25
    Par défaut
    @N_BaH : ici il n'y a que des doublons, mais en effet dans un autre fichier j'ai potentiellement des triplons (jusqu'à des septuplons, en fait).

    @CosmoKnacki : Les valeurs de la première colonne sont a conserver. Ce sont des ID. Pour les doublons, je leur affecte un nouvel ID non encore utilisé.

  7. #7
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par Semhur
    Voici mon code maintenant :
    mais, comment ça marche ?! il n'y a pas d'entrée pour awk.

    il n'y a pas besoin de lire le fichier ligne par ligne en shell pour utiliser awk : il sait lire des fichiers, même plusieurs, tout seul.

    Citation Envoyé par Semhur
    Pour les doublons, je leur affecte un nouvel ID non encore utilisé.
    c'est-à-dire ? il faudrait aussi trouver des manques dans la numérotation pour les attribuer d'abord aux doublons, et quand/si il n'y en a plus/pas, on utilise un indice supérieur à l'indice max ?

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 5 308
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    while read ligne; do
        awk 'BEGIN{FS=",";OFS=FS;} {$1=substr($1,2,length($1)-2);} (a[$1]>0){$1=max;} {a[$1]++;if ($1>=max) max=$1+1;} {$1="\""$1"\"";} 1' >> tempfile
    done < $fichier
    Tout de suite après avoir posté, j'ai eu peur de voir quelque chose comme ça.
    Non seulement tu n'as pas besoin du while, puisque c'est ce que fait awk, travailler ligne à ligne; Mais, en plus, cela ne marche pas du tout ! Car la mémoire est perdue entre 2 lignes.

    Ce qui était attendu comme code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    awk 'BEGIN{FS=",";OFS=FS;} {$1=substr($1,2,length($1)-2);} (a[$1]>0){$1=max;} {a[$1]++;if ($1>=max) max=$1+1;} {$1="\""$1"\"";} 1' fichier
    "5","Lorem","ipsum",
    "6","dolor","sit"
    "7","amet","consectetur"
    "8","adipiscing","elit."
    "9","Sed","non"
    "10","risus.","Suspendisse"
    "11","lectus","tortor,"
    "1","dignissim","sit"
    "12","amet,","adipiscing"
    "13","nec,","ultricies"
    "14","sed,","dolor."
    Et rien de plus !


    je leur affecte un nouvel ID non encore utilisé.
    Ah ! C'est N_BaH qui a raison. Il faut lire tout le fichier pour avoir un premier bilan.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ awk 'BEGIN{FS=",";OFS=FS;} (FNR==NR){a[$1]++;if (a[$1]==2) b[$1]=NR;v=int(substr($1,2,length($1)-2));if (max<v) max=v;next;} ((a[$1]>1)&&(FNR>=b[$1])){max++;$1="\""max"\"";} 1' fichier fichier
    "5","Lorem","ipsum",
    "6","dolor","sit"
    "7","amet","consectetur"
    "8","adipiscing","elit."
    "9","Sed","non"
    "10","risus.","Suspendisse"
    "11","lectus","tortor,"
    "1","dignissim","sit"
    "12","amet,","adipiscing"
    "13","nec,","ultricies"
    "14","sed,","dolor."

  9. #9
    Membre averti
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 25
    Par défaut
    Ok, ça fonctionne ! Merci beaucoup, je passe la discussion en Résolue.

    J'ai essayé de comprendre dans le détail, mais je n'ai pas réussi
    En gros, ce que j'ai compris :
    * le (FNR==NR) permet de travailler sur le fichier et sur l'équivalent d'une copie, ça permettra de conserver la mémoire du nombre max.
    * les tableaux a et b, là je n'ai pas tout pigé. Le b sert à conserver le n° de ligne du max trouvé ?
    * la suppression et réinsertion des guillemets, c'est grâce à int(substr($1,2,length($1)-2)); et $1="\""max"\"";

    Mais si tu as un peu de temps pour expliquer, je suis preneur

  10. #10
    Invité
    Invité(e)
    Par défaut
    par contre, le script de Flodelarab fait une renumérotation de certain "indices".
    ça a l'air de fonctionner parce que les doublons sont en fin de fichier, mais si ont déplace les doublons en milieu de fichiers les suivants sont réindexés.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ cat /tmp/lorem
    "5","Lorem","ipsum",
    "6","dolor","sit"
    "7","amet","consectetur"
    "5","amet,","adipiscing"
    "7","nec,","ultricies"
    "6","sed,","dolor." 
    "8","adipiscing","elit."
    "4","Sed","non"
    "10","risus.","Suspendisse"
    "11","lectus","tortor,"
    "1","dignissim","sit"
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ awk 'BEGIN{FS=",";OFS=FS;} {$1=substr($1,2,length($1)-2);} (a[$1]>0){$1=max;} {a[$1]++;if ($1>=max) max=$1+1;} {$1="\""$1"\"";} 1' /tmp/lorem
    "5","Lorem","ipsum",
    "6","dolor","sit"
    "7","amet","consectetur"
    "8","amet,","adipiscing"
    "9","nec,","ultricies"
    "10","sed,","dolor." 
    "11","adipiscing","elit."
    "4","Sed","non"
    "5","risus.","Suspendisse"
    "6","lectus","tortor,"
    "1","dignissim","sit"
    et cela crée d'autres doublons.

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

    Informations forums :
    Inscription : Septembre 2005
    Messages : 5 308
    Par défaut
    @N_BaH :

    Au temps pour moi. Il manque la fonction int( ) qui demande à awk de bien prendre en compte la variable comme un entier et pas une chaîne de caractères.
    Comme quoi, ce n'est pas si transparent.

    Mais si tu as remarqué la deuxième mouture qui fait le bilan avant le traitement, elle, elle ne contient pas l'erreur.

    Correctif du premier algorithme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $  awk 'BEGIN{FS=",";OFS=FS;} {$1=int(substr($1,2,length($1)-2));} (a[$1]>0){$1=max;} {a[$1]++;if ($1>=max) max=$1+1;} {$1="\""$1"\"";} 1' fichier2.txt
    "5","Lorem","ipsum",
    "6","dolor","sit"
    "7","amet","consectetur"
    "8","amet,","adipiscing"
    "9","nec,","ultricies"
    "10","sed,","dolor."
    "11","adipiscing","elit."
    "4","Sed","non"
    "12","risus.","Suspendisse"
    "13","lectus","tortor,"
    "1","dignissim","sit"
    @Semhur :

    Mais si tu as un peu de temps pour expliquer, je suis preneur
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    awk 'BEGIN{FS=",";OFS=FS;} (FNR==NR){a[$1]++;if (a[$1]==2) b[$1]=NR;v=int(substr($1,2,length($1)-2));if (max<v) max=v;next;} ((a[$1]>1)&&(FNR>=b[$1])){max++;$1="\""max"\"";} 1' fichier fichier
    (FNR==NR){blablabla;next;} {blablabla;}' fichier1 fichier2 est une structure classique pour awk.
    La première accolade ne s'applique que pour le premier fichier. La seconde accolade traite le deuxième fichier.
    Le premier fichier est donc un dictionnaire, une référence, une préparation. Et le second fichier, la vraie cible du traitement.
    Ici, les 2 fichiers sont le même.
    Donc on va dresser un bilan sur le premier fichier, et renuméroter le second en sortie.
    On aurait pu choisir un algorithme qui ne fait qu'une passe sur un fichier, mais le but est aussi d'ouvrir des horizons à ceux qui lisent.


    "a" est le tableau associatif qui compte le nombre d'occurrences.
    Mais ce n'est pas parce que "5" est cité 3 fois qu'il faut modifier les 3.
    Donc le tableau "b" stocke le numéro de ligne à partir duquel on modifie le numéro. ("a" a alors une valeur d'exactement 2)
    max est la plus grande valeur déjà utilisée dans la première partie du script. Elle sert de curseur pour renuméroter dans la deuxième partie de l'algorithme.
    Comme on a le tableau "a", on pourrait imaginer un autre algorithme, sans max, qui part de 1 et qui comble les "trous".


    Un autre classique de awk est le "1" final.
    1 est la condition.
    Mais 1 est toujours vrai.
    Donc il fait l'action.
    Mais il n'y a pas d'action. (pas d'accolade)
    Donc il fait l'action par défaut.
    Et l'action par défaut est l'affichage de la ligne.
    Donc "1" tout seul affiche la ligne en sortie. Ligne modifiée ou non.

    la suppression et réinsertion des guillemets, c'est grâce à int(substr($1,2,length($1)-2)); et $1="\""max"\"";
    Oui !
    Généralement, on peut considérer les variables comme chaîne de caractères ou nombre, à loisir.
    C'est très pratique.
    Mais on voit qu'ici, int( ) est indispensable car on a utilisé une fonction typique de chaînes de caractères (substr( ) ).
    cf plus haut.

  12. #12
    Invité
    Invité(e)
    Par défaut
    non, je ne vois pas.
    "risus.","Suspendisse" est renuméroté de 10 à 12, alors que dans le fichier original la valeur maximale de l'indice est 11...

    ce que j'ai compris, c'est qu'il faut réindexer uniquement les doublons, en tenant compte de la valeur maximale de l'indice du fichier, soit 11, le premier doublon devant être numéroté 12...

    Semhur ?
    Dernière modification par Invité ; 02/08/2018 à 20h17. Motif: renuméroté 12! pas 2 :(

  13. #13
    Expert confirmé Avatar de BufferBob
    Profil pro
    responsable R&D vidage de truites
    Inscrit en
    Novembre 2010
    Messages
    3 041
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : responsable R&D vidage de truites

    Informations forums :
    Inscription : Novembre 2010
    Messages : 3 041
    Par défaut
    salut,

    j'y vais de ma solution pour le fnu, assez similaire en awk :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #!/usr/bin/awk -f
    BEGIN {     # on prend les guillemets comme delimiteur
       FS = "\""
    }
     
    $2 > max {  # on conserve l'index le plus grand
       max = $2
    }
     
    {           # on enregistre chaque fin de ligne pour reutilisation
       line[NR] = sprintf("\"%s\",\"%s\"", $4, $6)
       id[NR] = $2
    }
     
    END {       # pour chaque ligne, si on a deja vu la valeur on incremente max
                # sinon on garde l'index. à la fin on affiche la ligne.
       for(i = 1; i <= NR; i++){
          if(seen[id[i]])
             id[i] = ++max;
          seen[id[i]]++
          printf("\"%d\",%s\n", id[i], line[i])
       }
    }
    (ou en oneliner awk -F'"' '$2>max{max=$2}{line[NR]=sprintf("\"%s\",\"%s\"",$4,$6);id[NR]=$2}END{for(i=1;i<=NR;i++){if(seen[id[i]])id[i]=++max;seen[id[i]]++;printf("\"%d\",%s\n",id[i],line[i])}}' fichier)

    et une version bash pour la route, sur le même principe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #!/bin/bash
    FILENAME="$1"
     
    while IFS='"' read _ id _ field1 _ field2 _; do
       index[n]=$id
       line[n]="\"${field1}\",\"${field2}\""
       if [[ $id -gt $max ]]; then max=$id; fi
       ((n++))
    done < "$FILENAME"
     
    ((max++))
     
    for ((i = 0; i < n; i++)); do
       if [[ -n "${seen[${index[i]}]}" ]]; then
          index[i]=$max
          ((max++))
       fi
       seen[${index[i]}]=1
       echo "\"${index[i]}\",${line[i]}"
    done

  14. #14
    Membre chevronné
    Profil pro
    Inscrit en
    Août 2010
    Messages
    345
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2010
    Messages : 345
    Par défaut
    Salut,
    En fonction de ma compréhension du problème qui correspond a celle de N_BaH.
    Une seule passe sur le fichier s'il n"est pas énorme.
    Code awk : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    awk '
    BEGIN {
    	FS=OFS=","
    	}
    $1 in a { 
    		$1="\"max\""
    		f=1
    	}
    	{
    		b[NR]=$0
    	}
    	f==0 {
    		a[$1]
    		gsub(/"/,"",$1)
    		c=int($1)>c?int($1):c
    	}
    	{
    		f=0
    	}
    END	{
    		for(i=1;i<=NR;i++) {
    			if(b[i] ~"^\"max\"") {
    				sub("max",++c,b[i])
    			}
    		print b[i]
    		}
    	}
    ' fichier.csv
    Cordialement.

  15. #15
    Invité
    Invité(e)
    Par défaut
    ça correspond à l'idée que je m'en faisais.

    Code awk : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    {
       if ($i in a) { ... }else{...} # évite de "jouer" avec un _f_lag ;)
       b[NR]=$0
    }

  16. #16
    Membre averti
    Profil pro
    Inscrit en
    Mars 2006
    Messages
    25
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2006
    Messages : 25
    Par défaut
    @Flodelarab : merci pour l'explication détaillée.

    @N_BaH : en utilisant la seconde solution proposée par Flodelarab, ça fonctionne : le premier indice doublon se trouve numéroté en 12.

    @N_BaH, BufferBob, @ctac_, @CosmoKnacki : merci pour toutes vos propositions !

    La solution de Flodelarab me convenant, je passe la discussion à Résolue.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Modifier la valeur d'un champs dans une table mysql en fonction du temps.
    Par VaSss dans le forum PHP & Base de données
    Réponses: 3
    Dernier message: 03/05/2010, 12h30
  2. Réponses: 0
    Dernier message: 02/12/2008, 10h34
  3. Réponses: 2
    Dernier message: 29/06/2008, 20h16
  4. Modifier les valeurs de certains champs d'une table
    Par Xorbane dans le forum Requêtes et SQL.
    Réponses: 6
    Dernier message: 22/10/2007, 23h10
  5. Réponses: 4
    Dernier message: 07/08/2006, 17h19

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