Blog ENI : Toute la veille numérique !
-25€ dès 75€ sur les livres en ligne, vidéos... avec le code FUSEE25. J'en profite !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

Gestion des répertoires et des fichiers

Introduction

Sur les systèmes Unix, il est souvent dit que tout est fichier. Cela s’applique notamment aux répertoires. Et les fonctions utilisables pour les fichiers le sont aussi pour les répertoires. Cependant, les répertoires étant des fichiers particuliers puisque le contenu n’est rien d’autre que la liste des fichiers qu’ils contiennent, nous disposons de quelques fonctions supplémentaires qui nous permettent de les ouvrir, de lire leur contenu entrée par entrée, de les créer simplement... Ce chapitre traitera des répertoires, ainsi que de leur contenu, les fichiers. Vous trouverez donc le nécessaire pour agir sur ceux-ci, à l’exception du contenu des fichiers qui fait l’objet du chapitre suivant.

Connaître le contenu d’un répertoire

Problème

Vous voulez connaître les noms des fichiers et des répertoires contenus dans un répertoire.

Solution

Servez-vous des fonctions opendir(), readdir() et closedir(). Utilisez-les de manière récursive dans les sous-répertoires si vous voulez aussi les parcourir.

Discussion

L’utilisation d’opendir() est simple. La difficulté réside dans ce que nous souhaitons faire des noms des fichiers et des répertoires obtenus avec readdir(). Voici un cas d’école, un exemple qui affiche les noms des fichiers et sous-répertoires d’un répertoire :


int 
printdir (char *dirname) 
{ 
  DIR *FD; 
  struct dirent *f; 
 
  if (NULL == (FD = opendir (dirname))) 
    { 
      fprintf (stderr, "opendir() impossible\n"); 
      return (-1); 
    } 
  printf ("%s :\n", dirname); 
  while ((f = readdir (FD))) 
    { 
      printf ("  %s\n", f->d_name); 
    } 
  closedir (FD); 
  return (0); 
}
 

Dans la boucle qui affiche les fichiers et répertoires, recourez à la recette "Obtenir des informations sur un fichier" pour afficher plus que le nom des fichiers, en particulier s’il s’agit d’un fichier ou d’un répertoire. Cela permet, pour parcourir le répertoire...

Effectuer une opération récursivement sur tous les fichiers d’un répertoire et de ses sous-répertoires

Problème

Vous souhaitez effectuer une opération sur tous les fichiers d’un répertoire ainsi que ses sous-répertoires.

Solution

Il n’y a pas de solution toute faite, vous devez programmer un algorithme de parcours des répertoires de manière récursive.

Discussion

Il existe plusieurs manières de parcourir les répertoires. La méthode la plus simple consiste à tester chaque fichier pour savoir si c’est un répertoire, et le cas échéant, appliquer l’algorithme à ce répertoire, de manière récursive. Cette méthode a l’avantage d’être simple mais pose un problème d’ordre de parcours. En effet, souhaitez-vous parcourir tous les fichiers et répertoires d’un même niveau ou pouvez-vous vous contenter de traiter les sous-répertoires d’un niveau au moment où ils se présentent, et ne continuer le niveau en cours que plus tard ?

Le programme suivant montre une méthode proche de celle de la commande find sur GNU/Linux, à base de récursivité et en utilisant chdir() qui nous place en permanence dans le répertoire en cours de traitement. Cette méthode utilise une liste chaînée pour obtenir la liste des fichiers d’un répertoire. Cela permet de faire un classement optionnel avant traitement. Dans notre cas, le classement consiste à traiter d’abord les fichiers, puis à aller dans les sous-répertoires de manière récursive.


#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <unistd.h> 
#include <dirent.h> 
#include <errno.h> 
 ...

Effacer un répertoire et tout son contenu

Problème

Vous souhaitez supprimer récursivement un répertoire et tous ses fichiers et sous-répertoires.

Solution

Appliquez la recette précédente en supprimant d’abord les fichiers en guise de traitement des fichiers, puis les répertoires après les avoir traités récursivement.

Discussion

La fonction rmdir(), contrairement à son équivalent en ligne de commande Unix avec l’option -r, ne permet pas de supprimer un répertoire non vide. Il faut donc le vider avant de faire appel à rmdir(). Nous sommes donc obligés d’appliquer la recette précédente afin de, pour chaque sous-répertoire, supprimer récursivement son contenu. Voici les modifications à appliquer à l’exemple de la recette précédente :


void action_dir (const char *dir) { } 
void action_dir_pre (const char *root, const char *dir) { } 
 
void 
action_dir_post (const char *root, const char *dir) 
{ 
  if (rmdir (dir)) 
    { 
      fprintf (stderr, "Impossible de supprimer le répertoire %s/%s " 
                       "(errno = %s)\n", root, dir, stderror (errno)); 
      exit (EXIT_FAILURE); 
    } 
} 
 
void 
action_file (const char *file) 
{ 
  if (unlink (file)) 
    { ...

Obtenir des informations sur un fichier

Problème

Vous voulez connaître la date de création, de modification, la taille, le type et autres informations sur un fichier.

Solution

Utilisez stat().

Discussion

La fonction stat() permet de récupérer toutes les informations intéressantes au sujet d’un fichier. Voici un exemple utilisant, entre autres, quelques macros prédéfinies spécialement pour tester le type de fichier, ainsi que les fonctions getpwnam() pour obtenir le nom d’un utilisateur à partir de son UID, et getgrnam() pour obtenir le groupe d’un utilisateur à partir de son GID.


#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <unistd.h> 
#include <time.h> 
#include <pwd.h> 
#include <grp.h> 
 
void 
print_fileinfo (char *filename) 
{ 
  struct stat buf; 
  struct passwd *pwd; 
  struct group *grp; 
 
  if (stat (filename, &buf)) 
    { 
      fprintf (stderr, "Problème avec stat()\n"); 
      exit (EXIT_FAILURE); 
    } 
 
  /* Type de fichier */ 
  if (S_ISREG (buf.st_mode)) printf ("Fichier régulier\n"); 
  else if (S_ISDIR (buf.st_mode)) printf ("Répertoire\n"); 
  else if (S_ISCHR (buf.st_mode)) 
                         printf ("Périphérique en mode caractère\n"); 
  else if (S_ISBLK (buf.st_mode)) 
                         printf ("Périphérique en mode bloc\n"); 
  else if (S_ISFIFO (buf.st_mode)) printf ("FIFO\n"); 
  else if (S_ISLNK (buf.st_mode)) printf...

Modifier la date de dernière modification d’un fichier

Problème

Vous souhaitez modifier la date de dernière modification d’un fichier.

Solution

Utilisez utime().

Discussion

La fonction utime() permet de modifier la date de dernière modification d’un fichier ainsi que sa date de dernier accès. Indiquez le nom du fichier en premier argument et NULL en second si la date souhaitée est la date courante. Sinon, vous devrez remplir la structure de type struct utimbuf :


struct utimbuf 
{ 
  time_t actime;                /* date de dernier accès */ 
  time_t modtime;               /* date de dernière modification */ 
};
 

L’exemple suivant vous permet de changer la date de dernière modification en laissant la date de dernier accès inchangée :


int 
change_mtime (const char *filename, time_t mtime) 
{ 
  struct stat buf; 
  struct utimbuf u; 
  if (-1 == stat (filename, &buffer)) 
    return (-1); 
 
  u.actime = buf.st_atime; 
  u.modtime = mtime; 
  return (utime (filename, &u)); 
}
 

Prototypes


#include <sys/types.h> 
#include <utime.h> 
 
int utime (const char *path, const struct utimbuf *times);
 

Voir aussi la recette "Obtenir des informations sur un fichier", en particulier pour connaître la date de dernière modification et la page de manuel de utime().

Créer un fichier

Problème

Vous voulez créer un fichier, éventuellement vide.

Solution

Utilisez open() ou fopen() selon vos besoins.

Discussion

Les fonctions open() et fopen() permettent de créer des fichiers si les bons arguments sont indiqués. open(fichier, O_CREAT|O_WRONLY) va créer le fichier, s’il n’existe pas déjà. open(fichier, O_CREAT | O_WRONLY | O_TRUNC) crée le fichier, en le vidant s’il existait déjà. De la même manière, fopen(fichier, "a") crée le fichier s’il n’existe pas déjà, et fopen(fichier, "w") le crée, en le vidant s’il existait déjà. Un appel à open(fichier, O_CREAT | O_EXCL) crée un fichier de façon dite exclusive. En effet, si le fichier existe déjà, ou s’il ne peut être créé, l’appel retourne une erreur. La norme C11 permet d’effectuer cet appel avec fopen(fichier, "x").

Les permissions peuvent être définies avec open() en troisième argument. C’est un OU logique des macros définies dans la recette précédente. Elles sont en lecture, écriture et exécution pour tout le monde lorsque nous utilisons fopen(). Ces permissions sont finalement modifiées, pour open() comme fopen(), par le masque de permissions fixé...

Renommer un fichier

Problème

Vous voulez renommer un fichier.

Solution

Utilisez la fonction rename().

Discussion

La fonction rename() permet de renommer un fichier ou un répertoire. Si celui-ci existe déjà, il est supprimé (sauf dans certains cas particuliers). Ces exceptions ont principalement lieu lorsque le système refuse l’opération (à cause des permissions par exemple), lorsque le fichier à écraser est un répertoire non vide ou utilisé par une autre application, ou encore quand le fichier à renommer est un fichier et que le nouveau nom correspond à un répertoire existant.

Prototypes


#include <stdio.h> 
 
int rename (const char *from, const char *to);
 

Copier un fichier

Problème

Vous voulez copier un fichier à un autre endroit.

Solution

Ouvrez le fichier à copier en lecture et sa copie en écriture, puis recopiez le contenu de l’un dans l’autre.

Discussion

Voici une fonction qui copie un fichier dans un nouveau fichier :


int 
copy (const char *existing_file, const char *new_file) 
{ 
 
  FILE *fn, *fe; 
  char buf[BUFSIZ]; 
  if (NULL == (fe = fopen (existing_file, "r"))) 
    { 
      fprintf (stderr, 
               "Impossible d'ouvrir le fichier '%s' en lecture\n", 
               existing_file); 
      return (-1); 
    } 
  if (NULL == (fn = fopen (new_file, "w"))) 
    { 
      fprintf (stderr, 
               "Impossible d'ouvrir le fichier '%s' en écriture\n", 
               new_file); 
      fclose (fe); 
      return (-1); 
    } 
 
  while (!feof (fe)) 
    { 
      ssize_t l; 
      l = fread (buf, sizeof (*buf), BUFSIZ, fe); 
      if (ferror (fe)) 
        { 
          fprintf (stderr, "Erreur de lecture\n"); 
          fclose(fn); 
          fclose(fe); 
          return (-1); 
        } 
      fwrite (buf, sizeof (*buf), l, fn); 
      if (ferror (fn)) 
        { 
          fprintf (stderr, "Erreur d'écriture\n"); 
          fclose(fn); 
          fclose(fe); 
          return (-1); 
        } 
    } 
  fclose(fn); 
  fclose(fe); 
  return (0); 
}...

Déplacer un fichier

Problème

Vous voulez déplacer un fichier à un autre endroit.

Solution

Sur un même système de fichiers, utilisez rename(). Si l’original et la copie se trouvent sur deux systèmes de fichiers différents, effectuez une copie (avec la recette précédente) puis supprimez le fichier original avec unlink().

Discussion

Sur un même système de fichiers, l’opération de déplacement consiste uniquement à supprimer une entrée dans le répertoire du fichier original, et à en ajouter une dans le répertoire de la copie. Le fichier en lui-même n’est pas touché.

Si l’opération a lieu sur deux systèmes différents, elle n’est pas possible comme décrite ci-dessus. Le fichier doit être recopié sur le nouveau système de fichiers. Par conséquent, le fichier original n’ayant plus lieu d’être, supprimez-le avec unlink(). Pour savoir si les systèmes de fichiers de l’original et de la copie sont les mêmes, dans le cadre d’un déplacement, le plus simple est d’utiliser rename() et de voir, si erreur il y a, si errno correspond à EXDEV. Si aucune erreur ne survient, le déplacement a eu lieu. Si cette erreur se manifeste, alors copiez puis supprimez l’original. Cela donne :


if (rename (original...

Supprimer un fichier

Problème

Vous voulez supprimer un fichier.

Solution

Utilisez unlink().

Discussion

La fonction unlink(), comme son nom l’indique, supprime le lien entre le fichier et le répertoire qui le contient. Elle supprime le fichier si ce lien vers le fichier était le seul.

Par contre, si d’autres liens existent, le fichier n’est pas supprimé. Si vous voulez l’enlever réellement, vous devez parcourir le système de fichiers à la recherche de fichiers ayant le même numéro d’inode, donc correspondant au même fichier sur le disque. Utilisez pour cela les techniques des recettes "Effectuer une opération récursivement sur tous les fichiers d’un répertoire et de ses sous-répertoires" et "Reconnaître que deux noms correspondent au même fichier" pour partir à la recherche des fichiers identiques.

Prototypes


#include <unistd.h> 
 
int unlink (const char *path);
 

Voir aussi la page de manuel de la fonction unlink().

Créer un répertoire

Problème

Vous voulez créer un répertoire.

Solution

Utilisez mkdir().

Discussion

Pour créer un répertoire, mkdir() fait le nécessaire, avec les permissions indiquées en second argument. Mettez par exemple la valeur octale 0755 pour un répertoire accessible à tout le monde en lecture, mais seulement à son propriétaire en lecture et écriture. Voyez la recette "Obtenir des informations sur un fichier" pour les macros correspondant aux permissions, que nous pouvons combiner avec un OU logique. Prenons un exemple d’un répertoire accessible uniquement à son créateur :


mkdir ("repertoire", S_IRWXU);
 

S’il s’agit de créer une arborescence, en d’autres termes un répertoire contenu dans des sous-répertoires à créer aussi de manière récursive, il faut parcourir tous les répertoires du chemin spécifié et créer ceux qui n’existent pas encore.


#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <errno.h> 
 
int 
mkdir_p (const char *path, mode_t mode) 
{ 
  char *d = strdup (path); 
  int i, len; 
  len = strlen (path); 
  for (i = 1; i < len; i++) ...

Créer un lien symbolique

Problème

Vous voulez créer un lien symbolique vers un fichier.

Solution

Utilisez symlink().

Discussion

Il existe deux fonctions pour créer des liens. Il s’agit de link() et symlink(). Ces deux fonctions diffèrent car dans le premier cas, une nouvelle entrée dans un répertoire pointant sur un fichier est créée, alors que dans le second, vous créez un lien symbolique. Dans le premier cas le lien se trouve obligatoirement dans le même système de fichiers, alors que dans le second cas, il peut se trouver dans un système de fichiers différents. Prenons un exemple :


symlink ("/opt/mon_appli/3.1.2", "/opt/mon_appli/current");
 

Prototypes


#include <unistd.h> 
 
int symlink (const char *name1, const char *name2); 
int link (const char *name1, const char *name2);
 

Voir aussi les pages de manuel des fonctions symlink() et link() ; l’implémentation du programme ls sur GNU/Linux.

Obtenir le répertoire courant

Problème

Vous voulez obtenir le chemin complet du répertoire courant.

Solution

Utilisez g_get_current_dir() de la bibliothèque glib ou codez quelques lignes autour de getcwd().

Discussion

La fonction g_get_current_dir() renvoie une chaîne de caractères contenant le répertoire courant, chaîne qu’il faut libérer avec g_free() après utilisation.

Prototypes


#include <glib.h> 
gchar *g_get_current_dir (void);
 

Si vous préférez utiliser getcwd(), vous devez fournir un espace mémoire à cette fonction, et s’il est trop petit, recommencer avec un espace plus grand. Pour avoir un programme robuste, préférez un code de ce genre :


char * 
my_get_cwd (void) 
{ 
  char *cwd; 
  int len = 64; 
  int r; 
  if (NULL == (cwd = malloc (len * sizeof *cwd))) 
    { 
      fprintf (stderr, "Espace mémoire insuffisant\n"); 
      exit (EXIT_FAILURE); 
    } 
  while ((NULL == (r = getcwd (cwd, len))) && (ERANGE == errno)) 
    { 
      len += 32; 
      if(NULL == (cwd = realloc (cwd, len * sizeof *cwd))) 
        { 
          fprintf (stderr, "Espace mémoire insuffisant\n"); 
          exit (EXIT_FAILURE); 
        } 
    } 
  if (r > 0) 
    return (cwd); 
  free (cwd); 
  return (NULL); 
}
 

Ce code renvoie NULL en cas d’erreur...

Reconnaître que deux noms correspondent au même fichier

Problème

Vous voulez tester si deux noms de fichiers correspondent au même fichier.

Solution

Utilisez stat() et comparez les numéros d’inode.

Discussion

Comme décrit dans la recette "Créer un lien symbolique", il est possible de créer plusieurs entrées dans plusieurs répertoires pour un même fichier, entrées appelées liens. Le seul moyen de constater que plusieurs fichiers ne sont en réalité que des liens vers le même fichier est de comparer leur numéro d’inode que vous pouvez obtenir avec stat(). Voici comment tester :


int 
are_files_the_same_file (const char *file1, const char *file2) 
{ 
  struct stat buf; 
  ino_t inode1, inode2; 
  if (stat (file1, &buf)) return (-1); 
  inode1 = buf.st_ino; 
  if (stat (file2, &buf)) return (-1); 
  inode2 = buf.st_ino; 
  if (inode1 == inode2) return (0); 
  return (1); 
}
 

Cette fonction renvoie 0 si les deux fichiers sont identiques et 1 s’ils sont différents. Elle renvoie -1 en cas d’erreur. Dans le cas d’une recherche des fichiers ayant le même numéro d’inode, il est préférable de déterminer le numéro de l’inode en question et d’utiliser une fonction comme celle-ci :


int 
has_file_given_inode...