Article publié dans Linux Magazine 74, juillet/août 2005.
diff
(une rustine, quoi)Pour produire un patch
, il faut faire un diff. La commande suivante
produit un fichier contenant l'intégralité des différences entre
les fichiers des deux arborescences passées en paramètre.
$ diff -Nru projet.new/ projet.HEAD/ > projet.patch
Le programme patch (écrit à l'origine par un certain Larry Wall) sait lire ce fichier rustine pour en appliquer le résultat à l'arborescence d'origine.
Si vous voulez récupérer les rustines individuelles (fichier source par fichier source), vous pouvez utiliser l'uniligne suivant :
$ perl -MIO::File -pe '*STDOUT=IO::File->new(sprintf"> patch.%03d", ++$i) if /^diff/'
On profite de la boucle implicite créée par l'option -p pour lire
le fichier de patch ligne à ligne et imprimer automatiquement chaque
ligne sur la sortie standard (STDOUT
). L'astuce consiste à changer le
fichier correspondant à STDOUT
à chaque fois qu'on détecte le début
d'un nouveau diff.
L'interface fournie par le module standard IO::File
et sa méthode
new
permet de retourner un filehandle à partir d'un nom de
fichier, IO::File
s'étant chargé d'ouvrir le fichier.
Or un filehandle est la seule chose que l'on puisse affecter à un
glob
(au sens de perl
) tel que *STDOUT
. C'est ce qui est fait.
Pour ceux qui s'inquiètent de l'utilisation des ressources, sachez que
les fichiers sont automatiquement fermés lors de l'association de
STDOUT
au fichier. Cela a été vérifié grâce à la commande lsof(1)
.
Maintenant que nous connaissons le principe de base, imaginons que, en
plein séance de compilation de RPM, nous modifions les sources en live
dans ~/rpm/BUILD/package/, avec une arborescence de référence dans
~/package. Les fichiers dans ~/rpm/BUILD étant effacés à chaque
recompilation par rpmbuild -ba package.spec
, nous tenons à obtenir
sous forme de patch (le format nécessaire à RPM) nos modifications.
Le réflexe premier est de faire un gros diff
:
$ diff -urN ~/package/ ~/rpm/BUILD/package/ | grep -v ^Binary > ~/tmp/mongros.patch
Déjà, on s'aperçoit que diff
rencontre des fichiers binaires dont il ne
sait que faire (d'où le grep
), mais il va aussi rencontrer tout ce
qui fichier texté créé par configure
, comme les Makefile, fichiers
de dépendance, etc. Le patch va donc être énorme, avec un quantité
industrielle de déchets (essayez).
Or, ce qui nous intéresse, ce sont essentiellement les fichiers .c
et
.h
qui ont été modifiés. Perl à la rescousse :
$ perl -MIO::File -pe 'if(/^diff/){$n=m!.*/(.*\.[ch])$! ? ">$1.patch" : ">/dev/null" ;*STDOUT=IO::File->new($n)}' mongros.patch
Là, ayant construit le nom de fichier ($n
) à ouvrir
(*STDOUT=IO::File->new($n)
) à partir des noms des fichiers
((.*\.[ch])$
) dans le diff, on obtient les trois patchs sur 50 qui
nous intéressent :
$ echo *.patch check_disk.c.patch check_smtp.c.patch check_ups.c.patch
Notez l'utilisation de l'opérateur m//
sous sa forme m!!
, pour
deux raisons : si on avait gardé la forme m//
, il nous aurait
fallu échapper le /
dans l'expression rationnelle, pour éviter que
perl
ne le confonde avec la fin de l'expression ; et comme le
shell utilise le même caractère que perl
pour les échappements
(\
), il nous aurait fallu l'échapper deux fois (\\/
). Les 47
rustines qui ne nous intéressent pas sont poubellisées grâce à ce cher
/dev/null
, bien pratique à utiliser.
Il nous faut néanmoins rajouter un test supplémentaire au début, de
façon à ne réouvrir un nouveau fichier qu'à la ligne commençant par
/^diff/
. Sinon, vos patches n'auront qu'une ligne, et leur contenu
sera parti à la poubelle (c'est du vécu).
Il ne nous reste plus qu'à concaténer nos trois fichiers pour avoir un joli patch à intégrer à notre package.spec :
$ cat *.patch > monpetit.patch
Une autre solution est de tout concaténer grâce à Perl :
$ perl -MIO::File -pe 'if(/^diff/){$n=m!.*/(.*\.[ch])$!?">>$ARGV.petit":">/dev/null";*STDOUT=IO::File->new($n)}' mongros.patch
Là, $ARGV
est utilisé pour récupérer le nom du fichier lu par
l'opérateur diamant <>
, lui-même induit par le commutateur
-p
passé à perl
.
Vous trouverez plus d'informations en consultant les pages de manuel
perlrun(1) et perlvar(1).
Ah, au fait, pourquoi faire compliqué quand on peut faire simple ?
Notre ligne de commande commence à sérieusement s'allonger, allons la
raccourcir en utilisant ce bon vieux open
:
$ perl -pe 'if(/^diff/){$n=m!.*/(.*\.[ch])$!?">>$ARGV.petit":">/dev/null";open STDOUT,$n}' mongros.patch
Ça fait quelques 23 caractères de gagnés, non négligeables pour les fainéants que nous sommes.
(Jérôme Fenal, Paris.pm - <jfenal@free.fr>
,
aidé de Philippe "BooK" Bruhat, Lyon.pm & Paris.pm - book@mongueurs.net
)
Si vous avez un répertoire mal rangé, une première approche de sa réorganisation peut être de classer les fichiers par date, dans des répertoires judicieusement nommés.
$ ls -l -rw-rw-r-- 1 book book 123 2005-05-14 17:21 bang_eth -rw-rw-r-- 1 book book 32 2005-05-14 16:54 clash -rw-rw-r-- 1 book book 1023 2005-05-12 10:07 clunk -rw-rw-r-- 1 book book 957 2005-05-19 11:18 crraack -rw-rw-r-- 1 book book 342 2005-05-19 15:15 kayo -rw-rw-r-- 1 book book 764 2005-05-12 10:07 pam -rw-rw-r-- 1 book book 8764 2005-05-19 15:10 powie -rw-rw-r-- 1 book book 723 2005-05-13 15:41 touche -rw-rw-r-- 1 book book 1760 2005-05-18 21:32 uggh -rw-rw-r-- 1 book book 3076 2005-05-19 15:15 zlonk
L'uniligne suivant va faire l'opération pour nous :
$ perl -MPOSIX=strftime -MFile::Path -e 'for(glob"*"){mkpath$d=strftime"%Y-%m-%d",localtime((stat)[9]);rename$_,"$d/$_"}'
La fonction strftime()
du module POSIX
permet d'afficher une
date en fonction d'un patron. mkpath()
fournie par File::Path
permet la création des répertoires.
Nous obtenons le résultat attendu :
$ tree . |-- 2005-05-12 | |-- clunk | `-- pam |-- 2005-05-13 | `-- touche |-- 2005-05-14 | |-- bang_eth | `-- clash |-- 2005-05-18 | `-- uggh `-- 2005-05-19 |-- crraack |-- kayo |-- powie `-- zlonk 5 directories, 10 files
Sachant que mkpath()
se comporte comme mkdir -p
(en créant les répertoires
intermédiaires si nécessaire), on peut même imaginer des patrons avec
plusieurs niveaux de profondeur, comme %Y/%m/%d
ou %Y/%U
(%U
,
%V
et %W
sont trois manières de compter les semaine dans l'année).
Attention, rename()
, tout comme son équivalent C (rename(2)) se contente de
renommer le fichier ; il ne saura pas le déplacer physiquement d'un
système de fichier à un autre si besoin est.
Pour faire des copies d'un système de fichier à un autre, il faut
utiliser File::Copy
, qui fournit des fonctions move()
et copy()
qui fonctionnent comme les commandes mv et cp usuelles.
(Mais ceci dépasse le cadre de cet uniligne.)
Merci à Jean-Charles Preaux, l'auteur de la question originale sur
la liste perl@mongueurs.net
.
(Philippe "BooK" Bruhat, Lyon.pm & Paris.pm - book@mongueurs.net
)
Envoyez vos perles à perles@mongueurs.net
, elles seront peut-être
publiées dans un prochain numéro de Linux Magazine.
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.