[shell + awk ?] Sorte de 'merge' entre fichiers
Le
Olivier Miakinen

Bonjour,
J'ai une demande assez compliquée, et je serais reconnaissant
à celui ou celle qui pourrait m'aider.
Je cherche à faire un script shell qui réalise une sorte de
'merge' entre fichiers, de la façon suivante. Soit par exemple
les cinq fichiers suivants :
./liste_chemins :
/un/chemin
/un/autre/chemin
/encore/un/chemin
/un/chemin/source :
((1 2 3 4 ) () () () )
((1 2 3 5 ) () () () )
((1 3 5 ) ("bidule") () () )
((1 3 5 2 ) () ("truc") () )
/un/autre/chemin/source :
((2 1 7 9 3 ) ("bidule") () () )
((2 1 7 9 4 ) ("bidule") () () )
/encore/un/chemin/source :
((1 1 2 1 ) () () () )
((1 1 2 1 1 ) () () () )
((1 1 2 1 2 ) () () () )
./reference :
((1 1 2 1 1 ) ("ref1") () () )
((1 2 3 ) ("ref2") ("ref2") () )
((1 2 3 4 ) ("ref3") ("ref3") () )
((1 2 3 6 ) () ("ref4") () )
((2 1 7 9 3 ) ("ref5") () () )
Je cherche à obtenir un fichier ./resultat qui contienne dans l'ordre
toutes les lignes de /un/chemin/source, /un/autre/chemin/source et
/encore/un/chemin/source, mais en remplaçant toute ligne par celle de
./reference à partir du moment où la liste de nombres du début de la
ligne est la même. C'est-à-dire :
./resultat :
((1 2 3 4 ) ("ref3") ("ref3") () )
((1 2 3 5 ) () () () )
((1 3 5 ) ("bidule") () () )
((1 3 5 2 ) () ("truc") () )
((2 1 7 9 3 ) ("ref5") () () )
((2 1 7 9 4 ) ("bidule") () () )
((1 1 2 1 ) () () () )
((1 1 2 1 1 ) ("ref1") () () )
((1 1 2 1 2 ) () () () )
J'aimerais bien que ça fonctionne sur un Solaris avec bourne shell ou
korn shell, et si possible en utilisant /usr/bin/nawk (ou éventuellement
/usr/xpg4/bin/awk), mais aussi sur AIX ou sur Linux avec les awk qu'on
y trouve. Si vraiment c'est trop compliqué avec awk, il est probable
que je puisse utiliser perl, mais comme je n'en suis pas certain je
préfèrerais vraiment awk. À moins bien sûr qu'il n'existe une autre
commande miracle qui fasse le 'merge' avec quelques options bien
choisies ! (C'est un problème réel, pas un exercice scolaire pour
apprendre à utiliser awk.)
Les fichiers sources peuvent être en nombre variable, mais leurs
chemins d'accès sont tous listés dans l'ordre dans le fichier
./liste_chemins . S'il est plus simple de commencer par créer
un fichier ./sources qui contienne tous les autres, je sais faire.
Par exemple :
for src in $(cat ./liste_chemins)
do
cat $src/source
done > ./sources
mais si on peut tout faire en une passe, c'est mieux.
Merci à qui aura eu le courage de lire jusque là, et qui saurait
me donner de bons conseils : je ne tiens pas forcément à ce qu'on
me fasse tout le boulot.
Cordialement,
--
Olivier Miakinen
J'ai une demande assez compliquée, et je serais reconnaissant
à celui ou celle qui pourrait m'aider.
Je cherche à faire un script shell qui réalise une sorte de
'merge' entre fichiers, de la façon suivante. Soit par exemple
les cinq fichiers suivants :
./liste_chemins :
/un/chemin
/un/autre/chemin
/encore/un/chemin
/un/chemin/source :
((1 2 3 4 ) () () () )
((1 2 3 5 ) () () () )
((1 3 5 ) ("bidule") () () )
((1 3 5 2 ) () ("truc") () )
/un/autre/chemin/source :
((2 1 7 9 3 ) ("bidule") () () )
((2 1 7 9 4 ) ("bidule") () () )
/encore/un/chemin/source :
((1 1 2 1 ) () () () )
((1 1 2 1 1 ) () () () )
((1 1 2 1 2 ) () () () )
./reference :
((1 1 2 1 1 ) ("ref1") () () )
((1 2 3 ) ("ref2") ("ref2") () )
((1 2 3 4 ) ("ref3") ("ref3") () )
((1 2 3 6 ) () ("ref4") () )
((2 1 7 9 3 ) ("ref5") () () )
Je cherche à obtenir un fichier ./resultat qui contienne dans l'ordre
toutes les lignes de /un/chemin/source, /un/autre/chemin/source et
/encore/un/chemin/source, mais en remplaçant toute ligne par celle de
./reference à partir du moment où la liste de nombres du début de la
ligne est la même. C'est-à-dire :
./resultat :
((1 2 3 4 ) ("ref3") ("ref3") () )
((1 2 3 5 ) () () () )
((1 3 5 ) ("bidule") () () )
((1 3 5 2 ) () ("truc") () )
((2 1 7 9 3 ) ("ref5") () () )
((2 1 7 9 4 ) ("bidule") () () )
((1 1 2 1 ) () () () )
((1 1 2 1 1 ) ("ref1") () () )
((1 1 2 1 2 ) () () () )
J'aimerais bien que ça fonctionne sur un Solaris avec bourne shell ou
korn shell, et si possible en utilisant /usr/bin/nawk (ou éventuellement
/usr/xpg4/bin/awk), mais aussi sur AIX ou sur Linux avec les awk qu'on
y trouve. Si vraiment c'est trop compliqué avec awk, il est probable
que je puisse utiliser perl, mais comme je n'en suis pas certain je
préfèrerais vraiment awk. À moins bien sûr qu'il n'existe une autre
commande miracle qui fasse le 'merge' avec quelques options bien
choisies ! (C'est un problème réel, pas un exercice scolaire pour
apprendre à utiliser awk.)
Les fichiers sources peuvent être en nombre variable, mais leurs
chemins d'accès sont tous listés dans l'ordre dans le fichier
./liste_chemins . S'il est plus simple de commencer par créer
un fichier ./sources qui contienne tous les autres, je sais faire.
Par exemple :
for src in $(cat ./liste_chemins)
do
cat $src/source
done > ./sources
mais si on peut tout faire en une passe, c'est mieux.
Merci à qui aura eu le courage de lire jusque là, et qui saurait
me donner de bons conseils : je ne tiens pas forcément à ce qu'on
me fasse tout le boulot.
Cordialement,
--
Olivier Miakinen
Quoique cela m'apporte des contraintes supplémentaires, je suis
en train de me demander si je ne pourrais pas me simplifier la
vie en faisant le travail une fois à la main, suivi par un diff,
puis en utilisant la commande patch. Il faut juste que je vérifie
si chaque ligne du fichier ./reference a bien son pendant dans
au moins l'un des fichiers /path/source, et si ces fichiers
/path/source ne peuvent que gagner des lignes (avec des numéros
nouveaux). Le fichier ./references, lui, ne bougera pas.
Un programme shell + awk sera toujours préférable car la solution
fonctionnera même si ces contraintes ne sont pas respectées, mais
sinon il est possible que j'aie une solution de repli.
Cordialement,
--
Olivier Miakinen
Regarde peut etre du côté de "join"
Le 12/04/2012 20:18, Alain Montfranc m'a répondu :
Je viens d'aller voir le man de join.
C'était une bonne idée, mais difficile à appliquer. D'une part parce
que le 'champ de fusion' (join field) ne peut pas être repéré par
de simples séparateurs de champs, d'autre part parce qu'il faut trier
les lignes, ce que je ne veux pas faire.
Merci en tout cas,
--
Olivier Miakinen
[...]
[...]
$ grep . *
a1:((1 2 3 4 ) () () () )
a1:((1 2 3 5 ) () () () )
a1:((1 3 5 ) ("bidule") () () )
a1:((1 3 5 2 ) () ("truc") () )
a2:((2 1 7 9 3 ) ("bidule") () () )
a2:((2 1 7 9 4 ) ("bidule") () () )
a3:((1 1 2 1 ) () () () )
a3:((1 1 2 1 1 ) () () () )
a3:((1 1 2 1 2 ) () () () )
r:((1 1 2 1 1 ) ("ref1") () () )
r:((1 2 3 ) ("ref2") ("ref2") () )
r:((1 2 3 4 ) ("ref3") ("ref3") () )
r:((1 2 3 6 ) () ("ref4") () )
r:((2 1 7 9 3 ) ("ref5") () () )
$ cat a? | awk -F'[)]' 'NR==FNR{a[$1]=$0;next}; $1 in a {$0=a[$1]};1' r -
((1 2 3 4 ) ("ref3") ("ref3") () )
((1 2 3 5 ) () () () )
((1 3 5 ) ("bidule") () () )
((1 3 5 2 ) () ("truc") () )
((2 1 7 9 3 ) ("ref5") () () )
((2 1 7 9 4 ) ("bidule") () () )
((1 1 2 1 ) () () () )
((1 1 2 1 1 ) ("ref1") () () )
((1 1 2 1 2 ) () () () )
--
Stephane
[...]
[...]
Pas besoin de cat:
awk -F'[)]' 'NR==FNR{a[$1]=$0;next}; $1 in a {$0=a[$1]};1' r a?
--
Stephane
[...]
(J'ai peut-être pas tout suivi de ton problème.)
Si tu es limité à awk, tu peux regarder du coté de getline, qui permet
de lire un fichier (ou plusieurs) dans une action. A ta place, je ferai
un petit fichier "driver" additionnel pour l'entrée de awk, avec des
lignes du style :
reference /.../reference
source /un/chemin/source
...
et dans les actions associées je lirais les lignes a la mano (avec
getline, donc). Ensuite, regexp et tout le tintouin, et on stocke tout
dans des tableaux.
Bref, c'est un peu dommage de devoir le faire en awk, mais a priori
c'est possible.
-- Alain.
[...]
Ah et sous Solaris, utiliser le nawk ou /usr/xpg4/bin/awk.
Il se peut que
awk -F'[)]' '
NR == FNR {a[$1]=$0;next}
$1 in a {$0=a[$1]}
{print}' r a?
Avec le vieux awk de /usr/bin toutefois.
--
Stephane
Ce que j'aime bien avec tes réponses, c'est qu'elles sont souvent
comme des petites énigmes de logique : il faut comprendre comment
ça marche (puisque ça marche), et quand on a compris c'est une
illumination !
Le 12/04/2012 21:30, Stephane Chazelas a écrit :
Ok, tu simplifies un peu les données, mais ça me va bien : la partie
difficile n'était pas là.
Tiens, c'est vrai qu'on n'a pas besoin de délimiteur de début. L'une
de mes objections à la suggestion d'Alain Montfranc saute par la même
occasion (mais pas la seconde).
Génial, ce test « NR==FNR » ! J'ai mis un peu de temps à le comprendre,
mais il signifie tout simplement « on est dans le premier fichier »
(donc dans r).
Voilà une méthode simple et de bon goût pour traiter un fichier d'une
certaine façon, et tous les autres fichiers d'une autre façon. Mais je
mettrai quand même un commentaire pour l'expliquer !
Quoi qu'il en soit, mon problème est résolu.
Oui, logique.
J'ai mis quelques secondes aussi à comprendre ce « 1 » qui est
équivalent à « 1 {print} » ou encore à « {print} ». Même si c'est
un peu plus long à écrire, je pense que j'emploierai la dernière
de ces trois méthodes : je la trouve plus lisible que la 1re.
Eh bien voilà. C'est si simple quand un Stéphane Chazelas nous
donne la solution !!!
Grand merci à toi,
--
Olivier Miakinen
C'est bien ce que j'avais commencé à faire, mais j'avais un peu de mal
à m'en sortir avant que Stéphane n'arrive avec son astuce NR==FNR.
Qu'est-ce qui n'est pas possible en awk, fût-ce au prix de contorsions
incroyables... ou d'astuces géniales ? ;-)
Cordialement,
--
Olivier Miakinen
Oui, en effet.
C'est bien le cas, d'ailleurs je l'avais précisé dans mon premier
article.
Possible, mais bon, je vais laisser ce bon vieux awk dormir, ça vaut
mieux.