Article publié dans Linux Magazine 66, novembre 2004.
Récemment, un collègue a eu besoin de découper un gros fichier en blocs de 65534 lignes (car Excel tronque les fichiers texte CSV qu'il importe à 65535, c'est embêtant).
Nous avons donc écrit le petit script suivant :
#!perl -wn BEGIN { $file = "partie00"; } if( $. % 65534 == 1) { # NOTE: $. commence à 1 close F; # ferme le fichier précédent open F, "> $file.csv" or die "Impossible de créer $file.csv: $!"; $file++; # auto-incrément magique } print F;
Notez l'utilisation de l'option -n pour ajouter une boucle implicite
sur les données en entrée, ainsi que l'utilisation de l'opérateur ++
sur une chaîne de caractères, qui incrémente magiquement (et avec propagation
des retenues !) la chaîne concernée.
L'avantage de ce script par rapport à split -l 65534 partie
, c'est que
l'on peut avoir un peu plus de contrôle sur le nom des fichiers
(partie00.txt, partie01.txt, etc. au lieu de partieaa, partieab)
et que mon collègue n'avait pas les outils GNU installés sur son
Windows 2000.
Puisque nous parlons de split, voici comment modifier le script pour gérer des parties à la taille mesurée en octets plutôt qu'en lignes.
La variable $/
(séparateur d'enregistrements en entrée) a une autre
spécificité (en plus de celles présentées dans l'article de GNU/Linux
Magazine 52) : si sa valeur est une référence à une constante numérique
n ou à une variable scalaire contenant le nombre n, l'opérateur
diamant renvoie un bloc de n octets. Voici le script précédent modifié
pour découper un gros fichier en morceaux tenant sur une disquette :
#!perl -wn BEGIN { $file = "partie00"; $/ = \1024; # lecture par blocs de 1 Ko $n = 0; } unless( $n++ % 1440 ) { # une disquette contient 1440 Ko close F; open F, "> $file.csv" or die "Impossible de créer $file.csv: $!"; $file++; } print F;
Merci à Didier Arenzana d'avoir d'aussi gros fichiers à traiter sur des
systèmes d'exploitation pas faits pour ça. ;-)
(Philippe "BooK" Bruhat, Paris.pm & Lyon.pm - book@mongueurs.net
)
Découper un fichier texte en morceaux, c'est bien, mais il y a des fois où on voudrait pouvoir simplement ne retenir qu'une partie du fichier, ne conserver qu'un bloc contenu entre certaines lignes. Il peut y avoir moyen de bricoler avec des outils comme tail(1) et head(1), mais pourquoi perdre du temps à s'escaguasser avec ça quand il est si facile de le faire en Perl.
Une première méthode simple et rapide est d'utiliser l'uniligne suivant :
$ perl -ne '18..21 and print' long_texte.txt
Dans ce cas-ci, il n'affichera que les lignes 18 à 21 du fichier long_texte.txt. Toutefois il serait plus pratique d'en faire un script auquel on pourrait passer les lignes à afficher en paramètres. Écrivons donc ce script, que nous nommons splice pour faire référence à la fonction du même nom en Perl, mais qui travaille elle sur les tableaux.
#!/usr/bin/perl my($first,$last) = (shift,shift); $.==$first .. $.==$last and print while <>
Quelques explications ne sont pas superflues.
Ce programme utilise l'opérateur flip-flop (..
) avec comme tests
de bascule des comparaisons sur le numéro de la ligne courante ($.
).
À noter que ..
n'agit comme un flip-flop qu'en contexte scalaire,
puisqu'en contexte de liste il est l'opérateur de génération de liste.
Dans l'uniligne montré au début on n'écrivait pas les tests $. == 18
car lorsque l'opérateur flip-flop reçoit des valeurs numériques en
arguments, il effectue tout seul la comparaison avec $.
. Comme
cela ne marche plus quand on remplace les valeurs par des variables,
il faut écrire le test explicitement.
Si on invoque ce script ainsi :
$ splice 185 202 long_texte.txt
il affichera les lignes 15 à 20 (incluses) du fichier long_texte.txt. On peut même l'utiliser dans un tube :
$ man perl | splice 319 322 NOTES The Perl motto is "There's more than one way to do it." Divining how many more is left as an exercise to the reader.
C'est pas mal, mais on peut faire mieux. Bien mieux. Si on change la manière d'indiquer les lignes à afficher, et qu'on adopte une syntaxe similaire à celle de cut(1), on peut alors indiquer plusieurs blocs de lignes.
#!/usr/bin/perl sub usage { print STDERR "usage: splice LINES [file ...]\n" and exit -1 } my $lines = shift || usage(); my(@first,@last,$i) = (); for my $block (split ',', $lines) { my @l = split '-', $block; push @first, $l[0]; push @last, $l[1] || $first[-1]; } ($.==$first[$i]||($.==$first[$i+1]&&++$i)) .. $.==$last[$i] and print while <>
L'exemple précédent s'écrit maintenant :
$ man perl | splice 319-322 NOTES The Perl motto is "There's more than one way to do it." Divining how many more is left as an exercise to the reader.
Plus intéressant, on peut maintenant indiquer plusieurs blocs de lignes à afficher. Pour illustrer cela, on crée d'abord un fichier qui ne contient que ses numéros de lignes :
$ pseq 1 20 "line %d" >text
ou, pour ceux qui n'auraient pas conservé la Perle correspondante :
$ perl -le 'print"line $_"for 1..20' >text
Exécutons maintenant splice en sélectionnant les lignes 8 à 9, 12 et 15 à 17.
$ splice 8-9,12,15-17 text line 8 line 9 line 12 line 15 line 16 line 17
Comme on le voit, seules les lignes indiquées sont affichées. Quant à ceux qui voudraient maintenant sélectionner des tranches non plus en fonction des numéros de lignes, mais en fonction du texte (en quelque sorte un mélange des fonctionnalités de splice et de grep(1)), il y a moyen de faire quelque chose, mais c'est plus délicat de trouver une manière générique de l'exprimer.
Par exemple l'uniligne suivant affiche le premier paragraphe de la section Author de perl(1) :
$ man perl | col -b | perl -ne '/AUTHOR/../^$/ and print' AUTHOR Larry Wall <larry@wall.org>, with the help of oodles of other folks.
Comme précédemment, tout repose sur l'opérateur flip-flop, mais en lui passant cette fois en arguments des expressions régulières. Les plus observateurs auront remarqué la présence de col(1) dans le tube afin de supprimer le formatage écran de man(1).
En suivant la même route que pour splice, il est simple de transformer cet uniligne en petit script mgrep (comme multi-grep :
#!/usr/bin/perl my($first,$last) = (shift,shift); /$first/../$last/ and print while <>
L'exemple précédent s'écrit alors :
$ man perl | col -b | sgrep 'AUTHOR' '^$' AUTHOR Larry Wall <larry@wall.org>, with the help of oodles of other folks.
L'étape suivante, accepter plusieurs expressions régulières, est celle qu'il est plus difficile de rendre aussi élégante que pour splice. En effet, dans l'idéal nous voudrions pouvoir accepter n'importe quelle expression régulière, mais certains caractères sont nécessaires pour la syntaxe de délimitation de ces expressions à passer en argument à mgrep (en reprenant celle de splice, on utilise le tiret pour délimiter les expressions d'un couple et la virgule pour délimiter les couples). Ces caractères ne pourront donc pas être utilisés au sein des expressions régulières, à moins de vouloir coder un mécanisme d'échappement. Nous nous en tenons à la syntaxe de splice, en connaissant et acceptant ses limitations.
#!/usr/bin/perl use strict; sub usage { print STDERR "usage: mgrep PATTERNS [file ...]\n" and exit -1 } my $patterns = shift || usage(); my(@first,@last,$i) = (); for my $block (split ',', $patterns) { my @l = split '-', $block; push @first, $l[0]; push @last, $l[1] || $first[-1]; } (/$first[$i]/||(/$first[$i+1]/&&++$i)) .. /$last[$i]/ and print while <>
Un exemple d'exécution de mgrep ressemblera à ceci :
$ man perl | col -b | mgrep AUTHOR-'^$',motto,virtues-why AUTHOR Larry Wall <larry@wall.org>, with the help of oodles of other folks. The Perl motto is "There's more than one way to do it." The three principal virtues of a programmer are Laziness, Impatience, and Hubris. See the Camel Book for why.
Les arguments signifient : afficher la ligne qui contient « AUTHOR »
et le paragraphe qui suit (paramètre AUTHOR-'^$'
), afficher la ligne
qui contient « motto » (paramètre motto
), afficher le texte de la
ligne qui contient « virtues » à la ligne qui contient « why »
(paramètre virtues-why
).
(Sébastien Aperghis-Tramoni (Maddingue), Marseille.pm - sebastien@aperghis.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.