Lecture et écriture de fichiers texte#
Les fichiers texte constituent le format d'échange le plus universel en informatique. Qu'il s'agisse de scripts de configuration, de journaux d'erreurs (logs) ou de rapports tabulaires, savoir lire et écrire du texte formaté est une compétence essentielle pour tout développeur C.
La bibliothèque standard <stdio.h> propose trois niveaux de granularité pour interagir avec les fichiers texte :
1. Caractère par caractère : fgetc() et fputc()
2. Ligne par ligne : fgets() et fputs()
3. Formaté : fscanf() et fprintf()
1. Ouverture et fermeture sécurisées#
Avant toute opération de lecture ou d'écriture, le fichier doit être ouvert via la fonction fopen(). Si le fichier demandé n'existe pas (en lecture) ou si l'utilisateur ne dispose pas des droits d'accès nécessaires, fopen() renvoie le pointeur nul NULL.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(void)
{
FILE *fichier = fopen("donnees.txt", "r");
if (fichier == NULL)
{
// perror() affiche la description humaine de l'erreur système
perror("Impossible d'ouvrir donnees.txt");
return EXIT_FAILURE;
}
// Traitement du fichier...
fclose(fichier);
return EXIT_SUCCESS;
}
[!IMPORTANT] Ne tentez jamais d'appeler
fclose()ou une fonction de lecture sur un pointeurFILE *égal àNULL. Cela provoquerait un crash immédiat du programme (Segmentation Fault).
2. Manipulation caractère par caractère : fgetc et fputc#
Ces fonctions sont idéales pour les opérations légères, le comptage brut ou le transcodage de texte.
int fputc(int c, FILE *stream): écrit le caractèrecdans le flux.int fgetc(FILE *stream): lit le caractère suivant et avance le curseur d'un octet. Renvoie la constanteEOF(End Of File, généralement-1) en fin de fichier ou en cas d'erreur.
Le piège du type de retour de fgetc#
[!WARNING] La fonction
fgetc()renvoie unintet non unchar! En C, uncharnon signé ne peut pas représenter la valeur négative deEOF(-1). Si vous stockez le résultat defgetc()dans une variable de typechar, la condition de fin de fichier risque de ne jamais être détectée, provoquant une boucle infinie.
// Exemple : Copie d'un fichier caractère par caractère
#include <stdio.h>
#include <stdlib.h>
void CopierFichier(const char *source, const char *destination)
{
FILE *src = fopen(source, "r");
if (src == NULL)
{
perror("Erreur ouverture source");
return;
}
FILE *dest = fopen(destination, "w");
if (dest == NULL)
{
perror("Erreur creation destination");
fclose(src);
return;
}
int c; // OBLIGATOIREMENT int pour comparer correctement avec EOF
while ((c = fgetc(src)) != EOF)
{
fputc(c, dest);
}
fclose(src);
fclose(dest);
printf("Copie terminee avec succes.\n");
}
3. Manipulation ligne par ligne : fgets et fputs#
Pour les fichiers textuels structurés en lignes (logs, listes de noms), la lecture ligne par ligne est bien plus performante et naturelle.
char *fgets(char *str, int num, FILE *stream): lit au plusnum - 1caractères depuis le flux et les stocke dansstr. La lecture s'interrompt dès qu'un saut de ligne (\n) est rencontré ou que la fin de fichier est atteinte.fgetsajoute toujours le caractère de fin de chaîne'\0'.int fputs(const char *str, FILE *stream): écrit la chaînestrdans le flux (sans ajouter automatiquement de saut de ligne, contrairement àputs).
Nettoyage du saut de ligne résiduel#
fgets() conserve le caractère \n dans le tampon si l'espace le permet. Pour le supprimer proprement :
#include <string.h>
void SupprimerSautLigne(char *chaine)
{
chaine[strcspn(chaine, "\r\n")] = '\0';
}
Exemple complet de lecture ligne par ligne#
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TAILLE_BUFFER 256
int main(void)
{
FILE *fichier = fopen("citations.txt", "r");
if (fichier == NULL)
{
perror("Erreur lors de l'ouverture");
return EXIT_FAILURE;
}
char ligne[TAILLE_BUFFER];
int numero_ligne = 1;
// fgets retourne NULL lorsque la fin du fichier est atteinte
while (fgets(ligne, sizeof(ligne), fichier) != NULL)
{
// Suppression du retour chariot
ligne[strcspn(ligne, "\r\n")] = '\0';
printf("[%03d] %s\n", numero_ligne, ligne);
numero_ligne++;
}
fclose(fichier);
return EXIT_SUCCESS;
}
4. Entrées/Sorties formatées : fprintf et fscanf#
Ces fonctions fonctionnent exactement comme leurs homologues de console printf et scanf, avec un premier paramètre supplémentaire désignant le pointeur FILE *.
Écriture formatée : fprintf#
FILE *rapport = fopen("rapport.txt", "w");
if (rapport != NULL)
{
char nom[] = "Alice";
float note = 17.5f;
fprintf(rapport, "%-15s | %5.2f / 20\n", nom, note);
fclose(rapport);
}
Lecture formatée : fscanf et son piège#
fscanf tente de faire correspondre le contenu du fichier avec une chaîne de format. Elle renvoie le nombre d'éléments correctement convertis et assignés.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fichier = fopen("notes.txt", "r");
if (fichier == NULL)
{
perror("Fichier introuvable");
return EXIT_FAILURE;
}
char nom[50];
float note;
// fscanf retourne le nombre de conversions réussies (ici 2)
while (fscanf(fichier, "%49s %f", nom, ¬e) == 2)
{
printf("Etudiant : %-15s - Note : %0.2f\n", nom, note);
}
fclose(fichier);
return EXIT_SUCCESS;
}
5. Le piège classique de l'anti-pattern feof()#
Beaucoup de débutants écrivent par réflexe :
// ATTENTION : CODE INCORRECT (Anti-pattern)
while (!feof(fichier))
{
fgets(buffer, sizeof(buffer), fichier);
printf("%s", buffer); // La dernière ligne sera affichée en double !
}
Pourquoi ce code est-il faux ?#
En C, l'indicateur de fin de fichier feof() ne passe à vrai qu'après qu'une opération de lecture a échoué en essayant de lire au-delà du dernier octet. Par conséquent, lors de la dernière itération :
1. feof() est encore faux.
2. fgets() tente de lire, rencontre la fin de fichier et échoue (en laissant l'ancien contenu dans le tampon).
3. Le programme continue et affiche le tampon précédent une seconde fois !
flowchart TD
A[Test !feof : Vrai car pas encore lu au-delà] --> B[Tentative de lecture fgets : Échec EOF]
B --> C[Affichage du buffer : Doublon de la dernière ligne !]
C --> D[Test feof au tour suivant : Vrai, sortie tardive]
[!TIP] La bonne pratique : Testez toujours directement la valeur de retour de la fonction de lecture dans la condition de votre boucle : -
while ((c = fgetc(fichier)) != EOF)-while (fgets(buffer, sizeof(buffer), fichier) != NULL)-while (fscanf(fichier, "...", ...) == NB_ATTENDU)
6. Mise en pratique : Analyseur statistique de texte#
Voici un programme complet qui lit un fichier texte, compte le nombre de lignes, de mots et de caractères, et affiche un rapport récapitulatif.
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdbool.h>
typedef struct
{
unsigned long lignes;
unsigned long mots;
unsigned long caracteres;
} StatistiquesTexte;
StatistiquesTexte AnalyserFichier(const char *chemin)
{
StatistiquesTexte stats = {0, 0, 0};
FILE *fichier = fopen(chemin, "r");
if (fichier == NULL)
{
perror("Impossible d'ouvrir le fichier pour analyse");
return stats;
}
int c;
bool dans_un_mot = false;
while ((c = fgetc(fichier)) != EOF)
{
stats.caracteres++;
if (c == '\n')
{
stats.lignes++;
}
// Détection des délimitations de mots
if (isspace(c))
{
dans_un_mot = false;
}
else if (!dans_un_mot)
{
dans_un_mot = true;
stats.mots++;
}
}
// Si le fichier n'est pas vide et ne se termine pas par \n, compter la dernière ligne
if (stats.caracteres > 0 && c != '\n')
{
stats.lignes++;
}
fclose(fichier);
return stats;
}
int main(void)
{
const char *chemin = "texte.txt";
// Création d'un fichier de test
FILE *test = fopen(chemin, "w");
if (test != NULL)
{
fprintf(test, "Le langage C est puissant et concis.\n");
fprintf(test, "Il constitue le fondement des systemes d'exploitation modernes.\n");
fprintf(test, "Apprenez a dompter ses pointeurs et ses flux de donnees.\n");
fclose(test);
}
StatistiquesTexte resultat = AnalyserFichier(chemin);
printf("==========================================\n");
printf(" RAPPORT D'ANALYSE : %s\n", chemin);
printf("==========================================\n");
printf(" Nombre total de caracteres : %lu\n", resultat.caracteres);
printf(" Nombre total de mots : %lu\n", resultat.mots);
printf(" Nombre total de lignes : %lu\n", resultat.lignes);
printf("==========================================\n");
return EXIT_SUCCESS;
}
7. Défi d'application#
Écrivez un programme nommé convertisseur_maj.c qui prend en entrée un fichier source et produit un fichier cible où toutes les lettres minuscules sont converties en majuscules (en utilisant la fonction toupper() de <ctype.h>), tout en conservant scrupuleusement la ponctuation et les retours à la ligne.
Commentaires