Programmation Perl
Fiche N°6 - Entrées et sorties
Opérations de base sur les fichiers
-	Ouvrir un fichier et l'associer à un descripteur (nom symbolique) : 
fonction open (Descripteur,Mode,NomDeFichier)

Le descripteur porte en général une majuscule (p.ex. IN, OUT, In, etc.).

Le mode indique l'orientation du flux : 
-	"<" pour la lecture
-	">" pour l'écriture d'un nouveau fichier (avec écrasement du fichier s'il existe).
-	">>" pour l'écriture à la fin du fichier existant (append)
Le mode peut être concaténé au nom de fichier :
open(OUT,">>sortie.txt');

Si Mode est omis, le fichier est ouvert en lecture.

Le NomDeFichier peut contenir tout un chemin d'accès, avec des car. slash ou backslash pour séparer les dossiers (mais sans les mélanger).

open(OUT,">","c:\\program files\\perl\\test.txt');	# avec backslash
open(OUT,">","c:/program files/perl/test.txt');		# avec slash
open(OUT,">","c:/program files\\perl/test.txt'); 	# invalide !!!

La fonction renvoie 0 si l'ouverture à échouée (fichier inexistant, répertoire inexistant, fichier protégé, etc.).

En général, on teste la réussite et on interrompt le programme s'il y a échec.

open(OUT,">","text.txt") || die "Impossible d'ouvrir test.txt !!!" ;

-	Fermer un fichier
fonction close (Descripteur);

-	Ecrire vers un fichier ouvert
fonction print (Descripteur, chaine);

-	Lire depuis un fichier ouvert
fonction read (Descripteur, chaine, longueur);
longueur caractères sont lus et placés dans le scalaire chaine. Chaque lecture a pour effet de faire avancer le pointeur de lecture.
-	Lire une ligne : l'expression <descripteur> renvoie la prochaine ligne (ou 0 si fin de fichier). Si le descripteur est omis, on lit une ligne à partir de l'entrée standard (le clavier).
while ($ligne=<IN>) {
	...
}
-	Lire tout un fichier d'un coup (shlurp) : 
Il faut indéfinir momentanément la variable $/ qui contient le séparateur d'enregistrement.
open (IN,"toto.txt");
my $svg=$/;
undef($/);
$fichier=<INPUT>;
$/=$svg;
Autre solution plus compacte.
	$fichier = do { local $/; <INPUT> };
Attention, si le fichier est trop gros, où si vous appliquez des expressions régulières sur le texte, cela peut être très consommateur de ressources.

-	Ouvrir un fichier avec un autre codage (p. ex. UTF-8)
Utiliser le module "Encode" (attention à la majuscule). Depuis Perl 5.8, les chaînes sont codées en Unicode (en interne). Lors des entrées/sorties, les chaînes sont converties en suite d'octets. Un codage particulier peut être déclaré dans le mode d'ouverture d'un flux, ce qui permet de définir une conversion automatique depuis/vers ce flux.

use Encode;
open (IN,"<:encoding(iso-latin-1)", $file); # ouverture en lecture d'un fichier codé ISO-latin-1
open (OUT,">:encoding(utf8)", $newFile); # ouverture en écriture d'un fichier codé UTF-8
cf. http://www.ayni.com/perldoc/perl5.8.0/lib/Encode.html
Les codages prédéfinis sont "utf8", "iso-8859-1", "ascii"

De la question du codage dépend la notion même de caractère (par exemple la suite Ã© peut être considérée comme deux caractères ISO ou un caractère UTF-8). Toutes les fonctions de traitement des chaînes (longueur, expressions régulières, etc.) en dépendent.
Pour se référer à des caractères Unicode dans des expressions régulières, on peut utiliser la syntaxe \x{....} avec le code hexadécimal sur deux octets entre les accolades. Pour utiliser tels quels des caractères utf8 dans les expressions régulières, il faut que le script soit lui-même encodé en UTF-8 (et non en ISO, comme c'est le cas par défaut). Il faut ajouter la directive "use utf8;"

-	Ouvrir un fichier en mode raw
Si on veut contrôler exactement le codage d'un fichier en écriture, le mode "raw" permet de supprimer tous les filtres qui aboutissent à des conversions automatiques.

Par exemple, par défaut, il y a une conversion automatique de LF (\xA) en CRLF (\xD\xA) même si on écrit :
print OUT "\xA" ou 	print OUT chr(10);
Pour éviter cela il faut ouvrir en mode le fichier en mode ">:raw"
open(OUT,">:raw","toto.txt");

-	Changer le codage au niveau des chaînes 

tr/\0-\xFF//CU;             # change Latin-1 to Unicode
tr/\0-\x{FF}//UC;           # change Unicode to Latin-1

-	Descripteur des entrées et sorties standard : STDIN et SDTOUT

Par défaut, les lecture/écriture sont effectuées à partir des entrée/sortie standards, à savoir le clavier et l'écran, identifiés par STDIN et STDOUT.

print STDOUT "hello"; 	# équivalent à print "hello"
$lire=<STDIN>;			# équivalent à $lire=<>;
-	Vider le tampon de sortie automatiquement
Quand on écrit dans un fichier, les données sont d'abord envoyées vers un tampon qui n'est écrit véritablement dans le fichier que lorsqu'il est plein (ou bien lorsque l'on ferme le fichier). Dans certains cas, il peut être intéressant de vider le tampon à chaque écriture. Pour cela il faut utiliser la fonction autoflush( ) du module IO::HANDLE

use IO::HANDLE;		# on importe le module IO::HANDLE
STDOUT->autflush( );	# à partir de maintenant tous les print vers l'écran seront instantanés

-	Lire un dossier
fonction opendir(Descripteur, NomDuDossier)
fonction readdir (Descripteur)
opendir(DIR,"c:/Mes documents";	# ouverture 
while (my $file=readdir(DIR)) {
	print $file."\n";
}
Remarque : le nom de fichier lu ne contient pas l'intégralité du chemin.

-	Tester l'existence d'un fichier ou d'un dossier
Tester l'existence d'un fichier : opérateur unaire -f
Tester l'existence d'un dossier : opérateur unaire -d 

if (- d "c:/Mes documents") {			# si le dossier existe
	opendir(DIR,"c:/Mes documents");	# ouverture 
	while (my $file=readdir(DIR)) {
		if (-f $file) {			# si $file est un fichier
			print $file."\n";
		}
	}
}

-	Créer/Supprimer un dossier
fonction mkdir(NomDuDossier );
fonction rmdir(NomDuDossier );

-	Supprimer un fichier (ou une liste de fichier)
fonction unlink(NomDuFichier );
Efface une liste de fichiers. Retourne le nombre de fichiers effacés avec succès.

-	Renommer ou déplacer un fichier ou un dossier
fonction rename(AncienNom,NouveauNom );

-	Copier un fichier
Il n'existe pas d'instruction Perl pour copier un fichier. Il faut faire appel à une commande du système d'exploitation (p. ex. le DOS), grâce à la fonction system.
fonction system(Commande, Arg1, Arg2, … )

system("copy","c:\\mes documents\\texte1.txt"," c:\\mes documents\\texte2.txt");

NB: sous Windows les backslashes sont ici obligatoires (compatibilité DOS).

-	Parcourir des dossiers et sous-dossiers
Le parcours d'une arborescence de dossier et sous-dossier peut-être effectué de manière récursive depuis la racine de cette arborescence. La fonction browseDir(dossier) est récursive. Pour éviter une boucle infinie, il faut prendre garde à ne pas "remonter" en appelant la fonction sur un dossier parent "..". Par ailleurs, on utilise un descripteur anonyme enregistré dans une variable locale.

sub browseDir {
	my $dir=shift;
	my $DIR;

	opendir($DIR,$dir);
	while ($file=readdir($DIR)) {
		if (-d "$dir/$file" && $file!~/^\./) {
			browseDir("$dir/$file");	# appel récursif
		} else {
			print "$dir/$file\n";		# si c'est un fichier, on print
		}
	}
	closedir($DIR);
}

Avertissement : ces commandes sont dangereuses : un script bogué pourrait effacer ou endommager le contenu de tout un dossier (voire toute une arborescence). Il est conseillé de faire tourner ces fonctions "à blanc" avant toute exécution réelle (en effectuant un print de la commande qui va être exécutée, plutôt que de l'exécuter).

Exercices :
- Télécharger l'archive Onu.ar-fr.zip et l'extraire dans un dossier nommé "corpus ONU".
- Ecrire un script rename.pl pour renommer tous les fichiers d'alignement de la forme "nom..fr-.ar.ces" en "nom.fr-ar.cesana".
- Ecrire un script arrange.pl pour créer 4 sous-dossiers "ar.translit", "ar.source", "fr", "fr-ar" et y déplacer respectivement les fichiers en arabe translittéré, les fichier en arabe UTF-8, les fichier français et les fichiers d'alignement. On écrira une fonction arrange(pattern, sourceDir, targert
- Créer une fonction splitFile("name.ext",size), qui découpe un fichier "name.ext" en n fichiers, nommés "name.1.ext"…"name.n.ext" contenant chacun size caractères (variante : size paragraphes). 
- Créer une fonction merge(dossier,pattern), qui crée un fichier résultant de la fusion de tous les fichiers du dossier dont le nom correspond au motif pattern
- Créer une fonction mergeRec(dossier,pattern) qui applique la fusion récursivement sur tous les sous-dossiers de Dossier

Pour gérer des fichiers compressés au format zip, utiliser le module Archive::Zip (http://aspn.activestate.com/ASPN/Modules/Perl/dist_html?dist_id=8323, ou cf. CPAN). Version compilée pour activePerl : téléchargement http://ppm.activestate.com/PPMPackages/zips/8xx-builds-only/ puis http://ppm.activestate.com/PPMPackages/zips/8xx-builds-only/Windows/Archive-Zip-1.20.zip )