| 
 17 janvier 2006 | 
mon_cat équivalente à la commande 
cat du système. Celle-ci copie sur sa sortie standard le contenu
des fichiers dont les noms lui sont donnés en argument.copy_data:Unix.file_descr -> Unix.file_descr -> unit,
 qui prend deux descripteurs
de fichier en argument et copie tout ce qui peut être lu sur le premier
dans le second, en utilisant les fonctions Unix.read et
Unix.write.
|     let buffer_size = 4096 let buffer = String.create buffer_size let rec copy_data fd_in fd_out = match Unix.read fd_in buffer 0 buffer_size with 0 -> () | r -> ignore (Unix.write fd_out buffer 0 r); copy_data fd_in fd_out;; | 
mon_cat, en utilisant la fonction précédente
sur chacun de ses arguments et en copiant dans la sortie standard
(Unix.stdout). Pour simplifier, on considèrera toute erreur de
lecture/écriture comme fatale.
| open Sys;; let mon_cat () = for i = 1 to Array.length argv - 1 do (* Les erreurs sont fatales dans cet exemple... *) let filename = Sys.argv.(i) in let fd_in = Unix.openfile filename [Unix.O_RDONLY] 0o400 in copy_data fd_in Unix.stdout; Unix.close fd_in done;; Unix.handle_unix_error mon_cat ();; | 
mon_tr qui remplace certains
 caractères reçus sur l'entrée standard avant de les retourner sur la
 sortie standard et qui a le même comportement que la commande
 tr du système (sans options). Par exemple, mon_tr  abcd ijk
 remplace les caractères a, b et c
 par, respectivement, i, j et k et le caractère d par k.copy_data précédente pour qu'elle prenne un
 argument supplémentaire, une fonction de type char -> char qu'elle
 applique sur chaque caractère lu avant de le copier.
 |     let buffer_size = 4096 let buffer = String.create buffer_size let copy_data fd_in fd_out translate = let rec copy_loop () = match Unix.read fd_in buffer 0 buffer_size with 0 -> () | r -> for i = 0 to r-1 do buffer.[i] <- translate buffer.[i] done; ignore (Unix.write fd_out buffer 0 r); copy_loop () in copy_loop () | 
tr_function : string -> string -> (char -> char) qui retourne une fonction de traduction
 qui traduit chaque caractère qui lui sera passé en argument selon la
 correspondance définie par les chaînes de caractères reçues en
 premier et second argument de la fonction tr_function (la longueur d'une chaîne de caractères est retournée par String.length). Pour
 effectuer la traduction, on utilisera une chaîne de caractères de
 longueur 256 (créée par la fonction String.create 256) telle que le caractère
 à la position i dans cette chaîne (s.[i]) corresponde à la
 traduction du caractère de code i (Char.code c).
 Inversement, le caractère de code i est obtenu par Char.chr i.
| let tr_function s1 s2 = let translation = String.create 256 in for i = 0 to 255 do translation.[i] <- Char.chr i done; let l1 = String.length s1 - 1 and l2 = String.length s2 - 1 in let l12 = min l1 l2 in for i = 0 to l12 do translation.[Char.code s1.[i]] <- s2.[i] done; for i = l12+1 to l1 do translation.[Char.code s1.[i]] <- s2.[l12] done; fun c -> translation.[Char.code c];; | 
Unix.stdin) et copie sur la sortie standard les données
après traduction. Pour simplifier, on considèrera toute erreur de
lecture/écriture comme fatale.
| open Sys;; open Unix;; let usage () = prerr_string ("Usage: "^(Filename.basename argv.(0))^" <input_string> <output_string>"); prerr_newline(); exit 1;; let buffer_size = 4096;; let mon_tr () = if Array.length argv <> 3 || String.length argv.(2) = 0 then usage() else begin let tr_char = tr_function argv.(1) argv.(2) in copy_data stdin stdout tr_char end;; handle_unix_error mon_tr ();; | 
mon_tail -n N file qui affiche les
 N dernières lignes du fichier régulier file. Pour cela, il faut
 lire le fichier en « sens inverse ».Unix.lseek et Unix.read, on lit les buffer_size caractères
précédents; on compte le nombre de retours à la ligne qu'ils
contiennent; s'il y en a au moins N+1, on affiche les N lignes
correspondantes et on termine; sinon, on recommence.really_read qui force la lecture
 d'exactement size caractères sur un descripteur déjà ouvert et qui
 les place dans une chaîne de caractères à partir d'un indice passé
 en argument. Si la lecture n'est pas possible, lever une exception
 End_of_file.
| open Unix;; let rec really_read desc buffer start length = if length <= 0 then () else match read desc buffer start length with 0 -> raise End_of_file | r -> really_read desc buffer (start+r) (length-r);; | 
set_pos : Unix.file_descr -> int -> unit qui 
positionne le descripteur de fichier à la position donnée en argument et
vérifie que la position d'arrivée est correcte. On
utilisera la fonction Unix.lseek pour se déplacer dans le fichier.
| let set_pos desc pos = if pos <> lseek desc pos SEEK_SET then failwith "lseek: mauvaise position de retour" | 
get_size : Unix.file_descr -> int qui 
retourne la taille du fichier associé à un descripteur.
| let get_size desc = (fstat desc).st_size | 
find_lines, telle que find_lines buffer size nb
retourne l'indice de début de la (nb-1)-ème ligne en partant de
la fin (size) de la chaîne de caractères buffer. Si la chaîne de
caractères contient moins de nb retours à la ligne la fonction lèvera
une exception contenant le nombre de lignes qu'il reste à lire.
| exception Not_enough_lines of int let find_lines buffer size nb = let rec find n index = if index < 0 then raise (Not_enough_lines n) else if buffer.[index] = '\n' then if n <= 1 then index + 1 else find (n - 1) (index - 1) else find n (index-1) in find nb (size - 1);; | 
tail, telle que 
tail desc size nb retourne la position absolue dans le fichier de la 
(nb-1)-ème ligne sur le descripteur 
desc à partir de la position absolue size. Attention, il faut faire
attention à ne pas demander de lire plus que la taille disponible sur
le descripteur sous peine que really_read lève une exception. 
| let buffer_size = 4096 let buffer = String.create buffer_size let rec tail desc size nb = if size > 0 then begin let real_size = min size buffer_size in let offset = size - real_size in set_pos desc offset; really_read desc buffer 0 real_size; try let index = find_lines buffer real_size nb in offset + index with Not_enough_lines missing -> tail desc offset missing end else 0;; | 
tail_lines telle que 
tail_lines desc nb affiche les nb dernières lignes du
descripteur desc, en ne tenant pas compte du dernier caractère
du fichier. On pourra aussi utiliser la fonction copy_data des
exercices précédents.
| let copy_data fd_in fd_out = let rec copy_loop () = match Unix.read fd_in buffer 0 buffer_size with 0 -> () | r -> ignore (Unix.write fd_out buffer 0 r); copy_loop () in copy_loop () let tail_lines desc nb = let pos = tail desc (get_size desc - 1) nb in ignore (lseek desc pos SEEK_SET); copy_data desc Unix.stdout | 
-f, équivalente à celle de la commande
tail du système, qui permet d'afficher les modifications apportées au
fichier au fur et à mesure quelles sont effectuées. Donner le principe
ce cette fonction.
N dernières lignes du fichier, on se
 positionne à la fin du fichier, et on essaye de lire (par
 Unix.read) à partir de là. Si read réussit à lire quelque
 chose, on l'affiche aussitôt et on recommence. Si read renvoie
 0, on attend un peu (Unix.sleep 1), puis on recommence.
Écrire une fonction monitor qui implante l'option -f. On pourra
utiliser une fonction récursive intermédiaire qui affiche toutes les
données lues entre la position courante du descripteur et la fin de
fichier. On supposera pour cette fonction que le fichier ne peut pas
être tronqué.
| let monitor desc size = ignore (lseek desc 0 SEEK_END); let buffer = String.create buffer_size in while true do let rec read_all() = match read desc buffer 0 buffer_size with 0 -> () | len -> ignore (write stdout buffer 0 len); read_all () in read_all(); sleep 1; done;; | 
|    let monitor desc size = let buffer = String.create buffer_size in let rec read_more last_pos = let rec read_all pos = match read desc buffer 0 buffer_size with 0 when pos > last_pos -> pos | 0 -> let pos = lseek desc 0 SEEK_END in if pos < last_pos then prerr_endline "Truncated"; pos | len -> ignore (write stdout buffer 0 len); read_all (pos + len) in let new_pos = read_all last_pos in sleep 1; read_more new_pos in read_more size;; | 
| open Sys;; open Arg;; let lines = ref 4;; let follow = ref false;; let usage () = prerr_string ("Usage: "^argv.(0)^" [options] <file>"); prerr_newline (); exit 1;; let process_file filename = let desc = openfile filename [ O_RDONLY ] 0 in tail_lines desc !lines; if !follow then monitor desc (get_size desc) else close desc;; let mon_tail () = let options = [ ("-n", Int (fun k -> lines := k)," line number"); ("-f", Set follow, "follow appended") ] in parse options process_file "file name";; Printexc.print (handle_unix_error mon_tail) ();; | 
mon_split n file qui découpe le fichier file
 en plusieurs fichiers (de noms file-0, file-1, etc.) de taille
 n (sauf éventuellement le dernier) et qui a un comportement
 équivalent à la commande split du système.n peut être
 très grande et donc qu'il n'est pas envisageable d'allouer un tampon
 de cette taille pour lire puis écrire en deux opérations. On
 utilisera donc un tampon de taille fixe pour lire les données. Il
 faut alors faire attention au cas ou la taille des fichiers est
 inférieure à la taille des données lues.copy_data des
exercices précédents en lui ajoutant en paramètre la quantité de caractères
à copier, et en retournant le nombre de caractères non-copiés (quand la 
fin du fichier est atteinte). 
| open Unix;; (* program constants *) let slice_size = ref 1024;; let buffer_size = 4096 ;; let buffer = String.create buffer_size (* name generation function for output files *) let slicename name index = Printf.sprintf "%s-%d" name index;; let rec copy_data fd_in fd_out tocopy = if tocopy > 0 then let len = Unix.read fd_in buffer 0 (min tocopy buffer_size) in if len > 0 then begin ignore (Unix.write fd_out buffer 0 len); copy_data fd_in fd_out (tocopy - len) end else tocopy else 0 (* do the job *) let split file size = Printf.fprintf Pervasives.stderr "file=%s size=%d" file size; prerr_newline(); let fd_in = Unix.openfile file [ O_RDONLY ] 0 in let rec copy_slices slice = let fd_out = Unix.openfile (slicename file slice) [O_WRONLY;O_CREAT;O_TRUNC] 0o666 in let left = copy_data fd_in fd_out size in Unix.close fd_out; if left = 0 then copy_slices (slice + 1) in copy_slices 0; Unix.close fd_in (* errors and arguments handling functions*) let errors = ref false;; let process_file file = try split file !slice_size with Unix_error (e,b,c) -> errors := true; prerr_string (Sys.argv.(0)^": " ^c^ ": " ^(error_message e)); prerr_newline ();; let set_size nb = if nb > 0 then slice_size := nb else raise (Arg.Bad "Size must be strictly positive");; let usage = "Usage: " ^ Sys.argv.(0) ^ " [ option ] filename";; (* main function *) let mon_split () = Arg.parse [ ("-b", Arg.Int set_size, "byte number") ] process_file usage; if !errors then exit 1;; handle_unix_error mon_split();; | 
mon_write qui se comporte comme write,
sans utiliser celle-ci! —on utilisera seuleument single_write. 
| open Unix let mon_write desc buf offset len = let rec write offset left = if len > 0 then let n = single_write desc buf offset left in write (offset + n) (left - n) else len in write offset len;; | 
single_write? Comment pourriez-vous
corriger celui-ci?
Partial_write et retourner à la fois l'erreur
et le nombre exact d'octets écrits (lorsque celui-ci est non nul, sinon
on reporte l'erreur comme d'habitude).
 | exception Partial_write of exn * int let mon_write desc buf offset len = let rec write offset left = if left > 0 then let n = begin try single_write desc buf offset left with | Unix_error (_, _, _) as exn when left < len -> raise (Partial_write (exn, len -left)) end in write (offset + n) (left -n) else len in write offset len;; | 
Ce document a été traduit de LATEX par HEVEA