split()
, mais comme toujours, il y a bien d'autres façons
de le faire en Perl. De plus split()
peut ne pas être
toujours le meilleur choix.
Par exemple, split()
n'est pas très adapté aux données de
taille fixe. Parfois la coupure doit avoir lieu au niveau de
caractères particuliers, mais dans ce cas il arrive que les champs
eux-mêmes contiennent le séparateur. Enfin, dans le cas du séparateur
"blanc", il peut également être utilisé simplement pour aligner les
champs. On peut envisager d'utiliser substr()
, mais cette
fonction ne donne accès qu'à un seul champ à la fois et de toutes
façons vous aurez toujours à traiter le cas des caractères blancs
superflus. Dans ces cas où les données ont une taille fixe, pensez à
utiliser pack()
. C'est la fonction la mieux adaptée.
ls -n
"
(i.e. "ls -lg
" dans laquelle les numéros d'utilisateur et
de groupe sont remplacés par leur valeur littérale). Elle utilise
pack()
et unpack()
pour manipuler la sortie
d'un ls
à la BSD.
$template = "a14 A9 A9 a*"; open(LS, "ls -lg |") || die "Can't ls!\n"; while (<LS>) { ($first, $uid, $gid, $last) = unpack($template, $_); $uids{$uid} = (getpwnam($uid))[2] unless ($uids{$uid}); $gids{$gid} = (getgrnam($gid))[2] unless ($gids{$gid}); (getgrnam($gid))[2]unless($gids{$gid}); print pack($template, $first, $uids{$uid},$gids{$gid},$last); }Ce code contient un bug subtile. Un cadeau et une surprise à celui qui le trouve.
Le premier argument à unpack()
est un modèle décrivant le
type et la taille de chaque champ. Les blancs contenus dans ce modèle
ne servent qu'à améliorer sa lisibilité, ils sont totalement ignorés
par unpack()
. Le premier, "a14" indique que le premier
champ est composé de 14 caractères ASCII (dans le cas de notre ls, il
correspond aux protections du fichier ainsi qu'au nombre de ses liens
durs). Il est suivi par 2 chaînes de 9 caractères (le nom de
l'utilisateur et du groupe), le "A" majuscule indique que
pack()
doit supprimer les blancs à la fin des
chaînes. Ainsi pourrons nous passer directement ce champ à n'importe
laquelle des fonctions get*nam()
. Le modèle "a*" indique
que le reste de la chaîne doit être renvoyé dans le dernier champ.
Remarquez que nous pouvons utiliser le même modèle si nous souhaitons
recréer la ligne par pack()
. L'interprétation que Perl
fait de "a" et "A" dans pack()
a été programmée à cet
effet. Le nombre après chaque opérateur dans le modèle, indique la
taille du champ : "a" le complète avec des caractères nuls, "A" avec
des blancs. "*" utilisé à la place d'un nombre donne au champ la
taille de la donnée traitée, ou de ce qu'il reste à traiter.
split()
peut également ne pas être le meilleur
choix si vos champs sont particulièrement irréguliers. Par exemple, le
précèdant artile de cette série a introduit
les expressions régulières pour traiter les champs d'enregistrements du type :
Pomeranz, Hal (pomeranz) x409Souvenez vous que les 2 derniers champs sont optionnels, que les séparateurs peuvent être des blancs, des tabulations ou les deux, et que la ligne peut également se terminer par un nombre quelconque de blancs. Le traitement devait en outre supprimer la virgule, le "x" devant le numéro de téléphone et les parenthèses autour de l'adresse E-Mail.
J'aurais pu utiliser split()
pour analiser la ligne
(l'exemple ci-dessous montre que le premier argument de split est une
expression régulière tout ce qu'il y a de plus normale)
@fields = split(/[\s,()]+/);Cependant j'aurais quand même eu a me débarasser du "x" dans le numéro de téléphone (il ne peut être traité comme un séparateur car il figure certainement dans un des noms des lignes à traiter). Je peux éliminer les caractères indésirables par :
s/\s+$//;De plus, que se serait-il passé si
split()
ne
m'avait retourné que 3 champs, le dernier aurait-il été l'adresse
E-mail ou le numéro de téléphone ? Bien sur on peut analyiser ce 3ème
champ et savoir s'il correspond a /x\d{3}/
, mais il
serait tellement plus commode de pouvoir écrire :
($last, $first, $email, $ext) = some_expressionet de récupérer
$email
ou $ext
vide si cette
information ne figure pas dans la ligne.
$_ = "Wed Apr 20 20:39:34 PDT 1994"; @fields = / ((\d+):(\d+):(\d+)) /;Il y a 4 sous-expressions, l'une contenant les 3 autres. La parenthèse ouvrante à l'extrême gauche définit l'expression englobant les 3 autres. Ainsi
$fields[0]
contient "20:39:34" et les 3
autres éléments de la liste contiennent respectivement "20", "39" et
"34".Le comportement de la recherche dans ce contexte de liste en fait un outil de découpage particulièrement souple. Inutile d'ajouter que dans ce cas où le résultat de la recherche est assigné à une liste, Perl n'instancie pas les variables $1,$2,...,$9.
Appliquons ce principe (*) à l'expression introduite dans notre précédent article :
bash$ cat essai.pl #!/usr/bin/perl $_="Pomeranz, Hal (pomeranz) x409"; ($first,$last,$junk1,$email,$ext) = /^(.+),\s*(\w+)\s*(\((\w+)\))?\s*(x(\d+))?\s*$/; print "\$first=",$first," \$last=",$last," \$email=",$email," \$ext=",$ext,"\n"; print "\$junk1=",$junk1,"\n"; bash$ essai.pl $first=Pomeranz $last=Hal $email=pomeranz $ext=x409 $junk1=(pomeranz) bash$Le champ junk1 est nécessaire parce que nous avons dû inclure dans des parenthèses le champ optionnel de l'adresse E-mail. Il y a donc eu génération d'une sous-expression supplémentaire. Larry Wall travaille à l'éviter, mais la solution n'arrivera sans doute pas avant une nouvelle release de Perl5.
On peut éviter ce champ intermédiaire en traitant les () du champ E-Mail comme des caractères optionnels hors du champ E-Mail optionnel. Ceci complexifie cependant quelque peu l'expression :
$bash cat essai1.pl #!/usr/bin/perl $_="Pomeranz,Hal (pomeranz) x409"; ($first,$last,$email,$ext) = /^(.+),\s*(\w+)\s*\(?(\w+)?\)?\s*(x(\d+))?\s*$/; print "\$first=",$first," \$last=",$last," \$email=",$email," \$ext=",$ext,"\n"; $bash essai1.pl $first=Pomeranz $last=Hal $email=pomeranz $ext=x409 bash$Outre l'avantage de supprimer les variables intermédiaires, cette expression n'utilise que les séparateurs naturels de la ligne qui sont, ",", "(" et ")" sans qu'il soit nécessaire d'avoir des blancs. En revanche elle accepte des expressions mal formées du type :
$_="Pomeranz,Hal (pomeranz x409";
"Pomeranz, Hal", Support, "Saratoga, CA, USA"où certains champs contiennent des apostrophes et d'autres non. Du fait que les expressions régulières de Perl ne sont pas régulières au sens strictement mathématique du terme, on ne peut pas les utiliser pour résoudre les cas de caractères ouvrant et refermant une expression. Ceci est encore plus difficile lorsque le délimiteur est composé de plusieurs caractères ou lorsqu'ils peuvent être imbriqués. L'expression d'une ligne correspondant aux commentaires du C représente la queste du Saint Grâal dans le monde de comp.lang.perl. En fait trouver cette expression tient de la résolution de la quadrature du cercle.
Un solution simple serait d'utiliser split()
pour
découper chaque enregistrement, en utilisant le séparateur ad-hoc,
puis de reconstruire les champs avec leur séparateur. Bien sur avec
cette approche, vous aurez à préserver les délimiteurs. Heureusement,
split()
facilite cette opération en utilisant une
parenthèse comme premier argument de façon à créer des
sous-expressions :
@list = split(/(,\s+)/);En supposant que la donnée
"Pomeranz, Hal", Support, "Saratoga, CA, USA"soit contenue dans la variable
$_,
, $list[0]
contiendra `"Pomeranz', $list[1]
',' etc. Il ne reste
plus qu'à rechercher les guillemets dans les champs pour les
réassembler.L'autre approche serait de rechercher un expression qui corresponde à chaque champ :
@fields = /("[^"]+"|[^,]+), \s+("[^"]+"|[^,]+), \s+("[^"]+"|[^,]+)/;Cette expression stipule que chaque champ est enclos dans des guillemets, (un guillemet, des caractères différents d'un guillement puis un guillemet), ou séparé par une virgule. Cette approche fonctionne tant qu'il n'y a pas d'imbrication des guillemets. De toutes façons, aussi bien
split()
que l'expression
régulière échoueront avec le cas pathologique suivant :
This ", would be" nastyUne solution universelle à ce type de problème nécessite une petite fonction. La distribution de Perl inclut le module
shellworks.pl
qui contient une fonction analisant des
lignes dont les séparateurs sont des blancs et dont certains champs
sont enclos dans des guillemets. J'en ai écris une version modifiée,
coteworks.pl
, qui accepte une expression régulière
quelconque pour délimiteur. Vous pouvez obtenir
shellworks.pl
d'une archive Perl ou en m'envoyant un
E-mail.
split()
donne de bons résultats pour les enregistrements
dont les champs ne contiennent pas le séparateur (un peu comme le
fichier /etc/passwd d'UNIX). Pour les données organisées en champs de
taille fixe, utilisez pack()
et unpack()
ou
substr()
si vous n'avez qu'un seul champ à
extraire. Enfin la recherche de motifs est toujours un bon outil
généric pour le découpage des données surtout si elles sont
particulièrement irrégulières. Souvenez vous que la gestion des
apostrophes et des guillemets est toujours difficile mais que ce problème a
déjà été traité. Ne cherchez pas à réinventer la roue.
Traduction de ;login: Vol. 19 No. 3, June 1994
Dernière édition: 16 mars 1997 phb Original 11/26/96ah |
|