Passage d'un tableau associatif bash vers awk

Tags: #<Tag:0x00007fd6de923d50> #<Tag:0x00007fd6de923be8>

Voilà mon besoin est tout simple mais sa résolution pas si simple :

  • j’ai un fichier csv, fic1.csv, au format date,heure,id
  • j’ai un 2nd fichier csv, fic2.csv, au format id, desc

Et je voudrais faire un peu comme en sql une jointure, c’est à dire avoir un nouveau fichier csv de la forme
date,heure,id, desc

A noter que fic1 fait des centaines de milliers de lignes, alors que fic2 a quelques dizaines de lignes.

Je voudrais faire la chose suivante dans un script shell :

  • je charge fic2 dans un tableau associatif tab
  • je passe tab comme parametre d’appel a awk
  • awk parse fic1 et dès qu’il rencontre un id présent dans tab, il ajoute la description

J’aimerais pouvoir faire ça mais d’après mes premières recherches j’ai l’impression que ça n’est pas trop possible ou alors pas de cette manière.

A noter que j’ai tenté une autre façon de faire mais qui est très très lente :
dans awk j’ai créé une fonction qui dans un appel systeme fait un grep dans fic2, ça marche mais c’est lourd et surtout très lent.

Merci pour vos lumières.
Lomic

j’oubliais de dire : même si ça n’est pas très propre, je me demandais si le plus simple et le plus performant ne serait pas que j’initialise en dur dans awk un tableau associatif qui contiendrait le contenu de fic2.
En fait fic2 n’est pas figé, je l’ai créé exprès pour mon besoin mais c’est flexible.

Salut,

peut-être qu’il n’est pas utile de passer par un tableau ?

J’ai bricolé vite fait un petit quelque chose pour l’exercice:

sputnik@debian:~/tests$ cat fic1.csv
2021-09-27,13:28,01
2021-09-27,13:27,02
2021-09-27,13:26,03
2021-09-26,13:25,02
sputnik@debian:~/tests$ cat fic2.csv
01,"premier item"
02,"deuxième item"
03,"troisième item"
sputnik@debian:~/tests$ cat script.sh
#!/bin/bash

# récupérer les id dans fic1.csv
for id in $(awk -F"," '{print $3}' fic1.csv) ; do
        echo -n "$id: "
        awk -v id="$id" -F"," '$0~id {print $2}' fic2.csv
done
sputnik@debian:~/tests$ ./script.sh
01: "premier item"
02: "deuxième item"
03: "troisième item"
02: "deuxième item"

Si tu veux faire une jointure, tu peux avoir quelque chose comme ça:

#!/bin/bash

# récupérer les id dans fic1.csv
for id in $(awk -F"," '{print $3}' fic1.csv) ; do
        echo -n $(awk -v id="$id" -F"," '$0~id {print $1","$2","$3","}' fic1.csv)
        awk -v id="$id" -F"," '$0~id {print $2}' fic2.csv
done

Mais ça marche mal avec des id qui sont présents plusieurs fois dans fic1:

./script.sh
2021-09-27,13:28,01,"premier item"
2021-09-27,13:28,01, 2021-09-27,13:27,02, 2021-09-27,13:26,03, 2021-09-26,13:25,02,"deuxième item"
2021-09-27,13:26,03,"troisième item"
2021-09-27,13:28,01, 2021-09-27,13:27,02, 2021-09-27,13:26,03, 2021-09-26,13:25,02,"deuxième item"

Bonjour

Une autre approche qui va utiliser le shell bash et la commande sed
pour rechercher/remplacer dans une copie du fichier (ou directement dans le fichier à modifier).

michel@debbull:~/tests$ cat fic1.csv
2021-09-27,13:28,01
2021-09-27,13:27,02
2021-09-27,13:26,03
2021-09-26,13:25,02
michel@debbull:~/tests$ 
michel@debbull:~/tests$ cat fic2.csv
01,"premier item"
02,"deuxième item"
03,"troisième item"
michel@debbull:~/tests$ 

Je lance la suite de lignes de commande suivante :

cp fic1.csv fic3.csv
sep=","
while read ligneId; do
    numId=${ligneId%%$sep*}
    sed -i "s/$sep$numId$/$sep$ligneId/" fic3.csv
done < fic2.csv

Et le résultat est :

michel@debbull:~/tests$ cat fic3.csv
2021-09-27,13:28,01,"premier item"
2021-09-27,13:27,02,"deuxième item"
2021-09-27,13:26,03,"troisième item"
2021-09-26,13:25,02,"deuxième item"
michel@debbull:~/tests$ 

Bien sûr, on peut se passer de faire une copie du fichier
et faire la modification directement dans fic1.txt
ce qui fait que la suite de lignes de commande devient :

sep=","
while read ligneId; do
    numId=${ligneId%%$sep*}
    sed -i "s/$sep$numId$/$sep$ligneId/" fic1.csv
done < fic2.csv

Tu as oublié de préciser si les id de fic1.csv sont tous dans fic2.csv. Si ce n’est pas le cas, il faut que la moulinette prenne en compte le fait qu’on peut se retrouver avec une description vide.

Généralement, quand j’évoque un code déjà produit dans la section « Programmation » de ce forum, je le mets dans mon message. Il ne faut pas en avoir honte, si ça se trouve, il n’y a pas grand chose à modifier pour obtenir le bon résultat.

Probablement, l’accès aux variables est plus rapide que l’ouverture de fichiers. Par contre, je ne sais pas comment tu veux faire ça, à voir donc si c’est plus simple.

Merci pour vos réponses qui effectivement sont intéressantes et permettent de résoudre le pb de façon élégante.

Ouais ta résolution est intéressante, pour corriger le pb je me demande s’il ne faut pas tout simplement inverser les boucles; je m’explique : dans un premier tu itères sur fic2.csv et tu récupères tous les ids et ensuite tu itères sur fic1.csv.
J’ai repris ton script et je l’ai transformé, ça donne :

#!/bin/bash

# récupérer les id dans fic2.csv
for id in $(awk -F"," '{print $1}' fic2.csv) ; do
	desc=$( awk -v id="$id" -F"," ' $1 ~ id {print  $2} ' fic2.csv)
    awk -v id="$id" -v description="$desc" -F"," '$3 ~ id {print $1","$2","$3","description}' fic1.csv
done

Et ça marche bien, à ceci près que l’ordre chronologique n’est pas respecté, ça donne :

2021-09-27,13:28,01,« premier item »
2021-09-27,13:27,02,« deuxième item »
2021-09-26,13:25,02,« deuxième item »
2021-09-27,13:26,03,« troisième item »

Mais avec un sort, ça résout le pb.
En tout cas merci pour le temps que tu as passé sur mon pb :wink:

Effectivement, encore plus direct et simple, merci.

Pas forcément, si tu enlèves l’option -i du sed, fic1.txt n’est pas modifié, les substitutions sont juste affichées sur la sortie standard (du coup pas besoin de fic3)

Et comme il y aura autant de lancements de la commande sed
que de lignes dans fich2, ça va faire énormément de lignes à afficher.