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