vendredi 16 janvier 2015

12-Les FICHIERS et les ENTREES/SORTIES: (input/output:)

>>>>
Liste des symboles rencontrés dans ce chapitre:
MAKE-PATHNAME *default-pathname-defaults* OPEN CLOSE READ-LINE READ READ-CHAR READ-BYTE READ-SEQUENCE WRITE-LINE WRITE-CHAR WRITE-STRING TERPRI FRESH-LINE PRINT PRIN1 PRINC WRITE-BYTE WRITE-SEQUENCE WITH-OPEN-FILE READ-FROM-STRING WITH-INPUT-FROM-STRING 

Les fichiers et les entrées/sorties.

Une entrée (input), c'est quand on lit (read) dans un fichier.
Une sortie (output), c'est quand on inscrit (print) dans un fichier.
Si on veut lire ou inscrire des données dans un fichier, il faut savoir sur quel fichier on travaille.
Il faut donc définir ce fichier et, peut-être le créer s'il n'existe pas.
Il faut aussi connaître le chemin qui mène au fichier (la suite des répertoires
depuis le répertoire racine jusqu'au répertoire contenant le fichier).
Common Lisp utilise la notion de PATHNAME pour définir le chemin et le fichier.
Nous reviendrons, plus loin, sur cette notion qui mérite un développement.
Pour l'instant, créons simplement un PATHNAME avec la fonction MAKE-PATHNAME:

(make-pathname :name "mon-fichier")
#P"mon-fichier" rend un pathname

ici, on utilise un seul mot-clé :name suivi du nom du fichier
En fait il y a 6 mots-clés, le reste est donc pris par défaut:

*default-pathname-defaults*
#P"/home/trochet/LISP/Clisp/" cette variable contient le chemin par défaut

En créant un pathname, il peut être utile de lui donner un nom.
Pour cela, on définit d'abord une variable qui contiendra le pathname:

(defparameter mon-path ())    ;ceci permet d'éviter une erreur dans ce qui suit

Puis:

(setf mon-path (make-pathname :name "mon-fichier"))
#P"mon-fichier"

ou bien, en une seule étape:

(defparameter mon-path (make-pathname :name "mon-fichier"))
MON-PATH

mon-path
#P"mon-fichier"

Une fois le PATHNAME défini, il faut ouvrir un flux (entrant, sortant ou les deux) vers le fichier:

(open mon-path :direction :output :if-exists :supersede)
      ceci ouvre un flux en sortie, donc pour écrire dans le fichier défini par mon-path.
      la clé :direction peut prendre les valeurs :output, :input (par défaut), :io (pour un flux bidirectionnel) ou :probe (création d'un flux sans direction, puis fermeture du flux avant d'être retourné par OPEN)
      la clé :if-exists indique ce qu'il faut faire si le fichier existe déjà:
      :supersede recrée un fichier de même nom, donc remplace le précédent

Comme il est utile d'avoir un nom de flux, on définit une nouvelle variable:

(defparameter sortie (open mon-path :direction :output :if-exists :supersede))
#...
           définit le flux "sortie"


Pour écrire dans ce flux:
(format sortie "Ma première donnée  de mon-fichier.~%")
NIL       format envoie une donnée dans le flux

(close sortie)
T il ne faut pas oublier de refermer le flux pour être sûr de l'inscription
dans le fichier

Notez que la variable "SORTIE" existe toujours, mais on ne peut plus l'utiliser pour écrire dans le fichier puisque le flux est fermé.

Si on veut, à nouveau, écrire dans le fichier, il faudra redéfinir "SORTIE" comme un flux ouvert.

Pour lire ce que l'on vient d'écrire dans le fichier, on crée un flux en entrée:

(defparameter entrée (open mon-path :direction :input))
#...


puis:

(read-line entrée)
"Ma première donnée  de mon-fichier."
NIL

Sans oublier:

(close entrée)
T

Dans ce qui suit, on encapsule ce qui vient d'être dit dans un LET.
Le PATHNAME est, ici, sous la forme d'une chaîne de caractères:
nous verrons que cela peut poser des problèmes de portabilité.
A vous de choisir un chemin et un fichier correspondant à vos répertoires.

OPEN:
READ-LINE:
CLOSE:

(let ((flux (open "/home/yannick/lisp/A lire"))) ;ouvre un flux en provenance d'un fichier
  (format t "~a~%" (read-line flux)) ;lit une ligne dans le fichier et l'affiche
  (close flux))           ;ferme le flux
Pour démarrer Lisp sous emacs: ceci est la ligne lue
T                     opération réussie

(let ((flux (open "/home/yannick/lisp/fich.txt" :if-does-not-exist nil)))
  (when flux ;ligne nécessaire pour la prise en compte de la clé-argument...
    (format t "~a~%" (read-line flux)) ;...:if-does-not-exist
    (close flux)))
NIL rend NIL, car le fichier fich.txt n'existe pas

(let ((flux (open "/home/yannick/lisp/A lire" :if-does-not-exist nil)))
  (when flux
    (loop for ligne = (read-line flux nil) ;itération pour chaque ligne lue ou fin de fichier (NIL)
 while ligne do (format t "~a~%" ligne)) ;écriture tant que la fin de fichier n'est pas atteinte
    (close flux)))       ;fermeture du flux
Pour démarrer Lisp sous emacs: le fichier, s'il existe, s'affiche entièrement
démarrer "GNU Emacs 23", puis "Alt + X slime" (ou M-x slime, si touche méta).
...       (J'ai coupé le résultat)
T

READ: c'est la fonction du REPL (Read Evaluate Print Loop), elle permet de lire le code source du lisp.

On suppose que le fichier name.txt contient les cinq lignes suivantes:
(1 2 3)
456
"a string" ;this is a comment
((a b)
      (c d))

(defparameter *s* (open "/home/yannick/lisp/lispbox/name.txt"))
*S* le parametre est un flux ouvert en provenance du fichier name.txt

(read *s*)
(1 2 3) READ lit chaque s-expression
(read *s*)
456
(read *s*)
"a string"         READ ne lit pas les espaces ni les commentaires
(read *s*)
((A B) (C D))
(close *s*)
T le flux est refermé

READ-CHAR:

(defparameter *s* (open "/home/yannick/lisp/lispbox/name.txt"))
*S* ouverture du flux

(read-char *s*)
#\( lit le premier caractère
(read-char *s*)
#\1 puis le deuxième
...
(close *s*)
T fermeture du flux

READ-BYTE: attention, le flux doit être ouvert avec la clé :element-type et le type '(unsigned-byte 8).

(defparameter *s* (open "/home/yannick/lisp/lispbox/name.txt" :element-type '(unsigned-byte 8)))
*S*                ouverture du flux

(read-byte *s*)
40 lit le premier caractère (format ANSI de "(" .)
(read-byte *s*)
49 puis le deuxième (format ANSI de "1")
...
(close *s*)
T fermeture du flux

READ-SEQUENCE: prend en argument une séquence et le flux et les mots-clés :start et :end.

(defparameter *data* (make-array 15 :initial-element nil))
*DATA*      définition d'un tableau dont les éléments sont initialisés à NIL

(read-sequence *data* (make-string-input-stream "chaine de caract") :start 2 :end 8)
8       lecture de caractères dans le flux
      ces caractères sont placés dans *data* entre les positions d'index 2 (compris) et 8 (non compris)
      (make-string-input-stream "chaine de caract") est le flux
      rend la position du dernier caractère écrit dans *data*

*data*
#(NIL NIL #\c #\h #\a #\i #\n #\e NIL NIL NIL NIL NIL NIL NIL) vérification

OPEN peut prendre les arguments suivants pour une sortie de données:
:direction avec pour valeur :output
:if-exist avec pour valeur :supersede, ou :append, ou :overwrite

(defparameter *s* (open "/home/yannick/lisp/lispbox/name.txt" :direction :output))
ERREUR      car le fichier existe déjà. Donc, cela ne permet que d'ouvrir
     des fichiers qui n'existent pas

WRITE-LINE: écrit une chaîne et passe à la ligne.

(defparameter *s* (open "/home/yannick/lisp/lispbox/name.txt" :direction :output :if-exists :append))
*S*      ouvre un flux sortant

(write-line "(a b c)" *s*)
"(a b c)"     écrit dans le fichier à partir de la fin du fichier
(close *s*)
T      fermeture du flux

(defparameter *s* (open "/home/yannick/lisp/lispbox/name.txt" :direction :output :if-exists :overwrite))
*S*      ouvre un flux sortant

(write-line "(a b c d)" *s*)
"(a b c d)"   écrase le fichier à partir du début du fichier
(close *s*)
T      fermeture du flux

(defparameter *s* (open "/home/yannick/lisp/lispbox/name.txt" :direction :output :if-exists :supersede))
*S*      ouvre un flux sortant et remplace le fichier

(write-line "(a b c d e)" *s*)
"(a b c d e)" l'ancien fichier a disparu
...
(write-line "\"autre chaîne\"" *s*)
"\"autre chaîne\"" notons les \ pour conserver les guillemets
(close *s*)
T fermeture du flux

WRITE-CHAR: écrit un simple caractère.

WRITE-STRING: écrit une chaîne de caractères sans passer à la ligne.

TERPRI: fait un passage à la ligne sans condition (TERminate PRInt)

FRESH-LINE: fait un passage à la ligne, sauf si l'index du flux y est
déjà. Permet d'éviter les sauts de ligne indésirables.

PRINT: écrit une s-expression précédée par une fin de ligne.

PRIN1: écrit une s-expression simplement

PRINC: permet d'écrire les chaînes sans les guillemets.

WRITE-BYTE: permet d'écrire des données binaires. Il faut, cependant,
ouvrir un flux avec OPEN et la clé :element-type et sa valeur
'(unsigned-byte 8).

WRITE-SEQUENCE:
Dans l'exemple qui suit le premier argument est la séquence-chaine "portable et pratique",
le 2ème argument est le flux *standard-output* (donc l'écran)

(write-sequence "portable et pratique" *standard-output* :start 3 :end 8)
table  rend la chaine comprise entre les index 3 (compris) et 8 (non compris)
"portable et pratique"  rend également la séquence

Attention! le code peut contenir un RETURN ou un RETURN-FROM avant la fermeture du flux (CLOSE). Et, s'il y a une erreur, le flux ne sera pas fermé et cela est problématique. Heureusement, la macro suivante permet de fermer le flux avant les RETURNs:

WITH-OPEN-FILE: (macro) construite sur un opérateur spécial UNWIND-PROTECT

(with-open-file (flux "/home/yannick/lisp/lispbox/name.txt")    ;le flux est ouvert, la procédure effectuée et le flux...
  (format t "~a~%" (read-line flux)))    ;...refermé
(a b c d e)                  cette procédure n'affiche que cela: la 1ère ligne!
NIL

(with-open-file (flux "/home/yannick/lisp/lispbox/name.txt" :direction :output :if-exists :append)
  (format flux "un texte"))
NIL        "un texte" est écrit à la fin du fichier sans les guillemets

Ecriture dans un fichier fibo.lisp:

(with-open-file
    (flux "fibo.lisp" :direction :output :if-exists :supersede)
  (format flux "~s~2%~s~%" '(format t "~&Bonjour~%")
  '(format t "Suite fibonacci jusqu'à 10: ~a~%" (suite-fibo 10))))
NIL             ouvre un fichier fibo.lisp (si le fichier existe déjà, il est d'abord détruit)
    et y inscrit le texte passé en argument dans le 1er FORMAT

Lecture dans le fichier fibo.lisp:

(with-open-file
    (flux "fibo.lisp" :direction :input)
  (do ((ligne (read-line flux nil :eof) (read-line flux nil :eof))
(k 0 (1+ k)))
      ((eq ligne :eof) 'fin)
    (format t "ligne ~a: ~a~%" k ligne)))
ligne 0: (FORMAT T "~&Bonjour~%")
ligne 1:
ligne 2: (FORMAT T "Suite fibonacci jusqu'à 10: ~a~%" (SUITE-FIBO 10))
FIN

Dans le DO on observe deux variables ligne et k.
ligne est initialisée par un premier appel à (read-line flux nil :eof) sa valeur suivante est donnée par un second appel à (read-line flux nil :eof).
k est initialisée à 0 et sa valeur suivante s'obtient par incrémentation de 1. k représente un numéro de ligne.
Le test de fin d'itération du DO est ((eq ligne :eof) 'fin). La fin d'itération est donc effective quand ligne contient la fin de fichier :eof.

READ-LINE prend un flux comme 1er argument; le 2ème argument indique si, oui ou non,
il y a production d'une erreur en fin de fichier et, sinon, 3ème argument, ce qu'il faut rendre:
Ici, le 2ème argument est NIL, donc pas d'erreur et :eof est rendu en fin de fichier (3ème argument).
Le fichier fibo.lisp est en fait un programme qu'on peut charger et exécuter:

(load "fibo.lisp")
Bonjour
Suite fibonacci jusqu'à 10: (0 1 1 2 3 5 8 13 21 34 55)
T     la fonction suite-fibo doit évidemment existée pour donner ce résultat
Vous pouvez essayer de mettre la définition de SUITE-FIBO dans le fichier fibo.lisp

Il peut arriver que le flux ne soit pas précisé.
Dans ce cas, c'est un flux par défaut qui s'active:
*standard-output* en sortie et *standard-input* en entrée. Exemple:

(progn
  (format t "Monsieur?... ")
  (read-line))
 Monsieur?... De La Fayette

"De La Fayette"
NIL

READ-FROM-STRING:

(read-from-string "1 2 3" nil 'fin :start 2 :end 3)
2   lit et affiche le 1er caractère à partir de l'index 2...(l'affichage s'arrête s'il y a un espace à l'index suivant)
3   ...et rend le dernier index lu (:end)

(read-from-string "1 2 3" nil 'fin :start 5 :end 5)
FIN  rend l'indication de fin de chaine conformément aux 2ème (NIL) et 3ème argument ('fin)
5   rend la valeur du dernier index testé (qui ne doit pas être supérieur à la longueur de la chaine)

(read-from-string "(1 2 3)")
(1 2 3)   toute la parenthèse est rendue...(même s'il y a des espaces)
7      ...et l'index se retrouve à 7.
                  ici, c'est donc la liste qui est rendue. Sans les parenthèses, c'est différent.

En fait, la chaine est lue depuis l'index donné par :start jusqu'à l'index donné par :end, à moins de rencontrer un espace:

(read-from-string "123 456" nil 'fin :start 0 :end 6)
123  rend 123 et non 123 45
4  rend l'index du caractère lisible suivant (ici 4 et non pas 6)

WITH-INPUT-FROM-STRING:
prend son argument comme un flux.

(with-input-from-string (str "Arbre")
   (loop for i from 0
for char = (read-char str nil nil)
while char
do (format t "~a - " char)))
A - r - b - r - e - 
<***


La prochaine fois:
             Les noms de chemins: PATHNAMES OBJECTS.

Aucun commentaire:

Enregistrer un commentaire