Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

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

10 réponses
Avatar
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

10 réponses

Avatar
Olivier Miakinen
Le 12/04/2012 17:49, j'écrivais :

J'ai une demande assez compliquée, et je serais reconnaissant
à celui ou celle qui pourrait m'aider.

[...]

À moins bien sûr qu'il n'existe une autre commande miracle qui
fasse le 'merge' avec quelques options bien choisies !

[...]



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
Avatar
Alain Montfranc
Olivier Miakinen a utilisé son clavier pour écrire :
Le 12/04/2012 17:49, j'écrivais :

J'ai une demande assez compliquée, et je serais reconnaissant
à celui ou celle qui pourrait m'aider.

[...]

À moins bien sûr qu'il n'existe une autre commande miracle qui
fasse le 'merge' avec quelques options bien choisies !

[...]



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,



Regarde peut etre du côté de "join"
Avatar
Olivier Miakinen
Bonjour,

Le 12/04/2012 20:18, Alain Montfranc m'a répondu :

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. [...]



Regarde peut etre du côté de "join"



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
Avatar
Stephane Chazelas
2012-04-12 17:49:02 +0200, Olivier Miakinen:
[...]
------------------------------
((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 ) () () () )
------------------------------


[...]

$ 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
Avatar
Stephane Chazelas
2012-04-12 20:30:10 +0100, Stephane Chazelas:
[...]
$ cat a? | awk -F'[)]' 'NR==FNR{a[$1]=$0;next}; $1 in a {$0=a[$1]};1' r -


[...]

Pas besoin de cat:

awk -F'[)]' 'NR==FNR{a[$1]=$0;next}; $1 in a {$0=a[$1]};1' r a?

--
Stephane
Avatar
Alain Ketterlin
Olivier Miakinen <om+ writes:

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 :


[...]
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 :



(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.
Avatar
Stephane Chazelas
2012-04-12 20:35:44 +0100, Stephane Chazelas:
2012-04-12 20:30:10 +0100, Stephane Chazelas:
[...]
> $ cat a? | awk -F'[)]' 'NR==FNR{a[$1]=$0;next}; $1 in a {$0=a[$1]};1' r -
[...]

Pas besoin de cat:

awk -F'[)]' 'NR==FNR{a[$1]=$0;next}; $1 in a {$0=a[$1]};1' r a?


[...]

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
Avatar
Olivier Miakinen
Bonjour,

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 :

$ 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") () () )



Ok, tu simplifies un peu les données, mais ça me va bien : la partie
difficile n'était pas là.

$ cat a? | awk -F'[)]'



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).

'NR==FNR{a[$1]=$0;next};



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.

$1 in a {$0=a[$1]};



Oui, logique.

1' r -



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.

((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 ) () () () )



Eh bien voilà. C'est si simple quand un Stéphane Chazelas nous
donne la solution !!!

Grand merci à toi,
--
Olivier Miakinen
Avatar
Olivier Miakinen
Le 12/04/2012 21:38, Alain Ketterlin m'a répondu :

[...]

Si tu es limité à awk, tu peux regarder du coté de getline, [...]

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.



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.

Bref, c'est un peu dommage de devoir le faire en awk, mais a priori
c'est possible.



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
Avatar
Olivier Miakinen
Le 12/04/2012 21:43, Stephane Chazelas se répondit :

Pas besoin de cat:

awk -F'[)]' 'NR==FNR{a[$1]=$0;next}; $1 in a {$0=a[$1]};1' r a?





Oui, en effet.

[...]

Ah et sous Solaris, utiliser le nawk ou /usr/xpg4/bin/awk.



C'est bien le cas, d'ailleurs je l'avais précisé dans mon premier
article.

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.



Possible, mais bon, je vais laisser ce bon vieux awk dormir, ça vaut
mieux.