Menu

Lire et écrire un fichier en Golang : os, bufio et répertoires

Comment lire et écrire des fichiers en Go : os.ReadFile et os.WriteFile, la lecture ligne par ligne avec bufio.Scanner, l'ajout en fin de fichier avec os.OpenFile, vérifier si un fichier existe, et travailler avec des répertoires.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Lire et écrire un fichier entier

os.WriteFile et os.ReadFile couvrent la plupart des besoins. Elles ouvrent, écrivent ou lisent, et ferment en un seul appel.

Les exemples de cette page travaillent dans un répertoire temporaire créé par os.MkdirTemp et le suppriment avec defer os.RemoveAll(dir), donc ils ne laissent rien derrière eux. Dans votre propre code, un chemin relatif comme "config.json" est résolu par rapport au répertoire de travail du processus, qui n'est pas forcément celui du fichier source ou du binaire.

os.WriteFile crée le fichier si nécessaire et le tronque s'il existe. Le troisième argument est la permission Unix d'un fichier nouvellement créé : 0o644 signifie que le propriétaire peut lire et écrire, tous les autres lire. Il est ignoré pour un fichier qui existe déjà, et l'umask du processus peut retirer des bits.

os.ReadFile lit tout en mémoire. C'est le bon choix pour des fichiers de configuration et de petites entrées, et le mauvais pour un log de plusieurs gigaoctets.

Lire ligne par ligne avec bufio.Scanner

Pour les gros fichiers, ou quand vous voulez de toute façon des lignes, utilisez bufio.Scanner. Il lit par blocs et vous donne une ligne à la fois, sans le retour à la ligne.

Trois détails :

  • Vérifiez sc.Err() après la boucle. Scan renvoie false aussi bien en fin de fichier qu'en cas d'erreur, et seul Err fait la différence.
  • La limite de 64 Ko par ligne. Par défaut, une ligne de plus de 64 Ko arrête le scanner avec bufio.Scanner: token too long. Pour les fichiers à longues lignes (JSON minifié, certains logs), relevez la limite avant la boucle : sc.Buffer(make([]byte, 1024*1024), 10*1024*1024).
  • Autres unités. sc.Split(bufio.ScanWords) produit des mots ; bufio.ScanRunes produit des caractères.

Pour lire un flux par blocs de taille fixe plutôt que par lignes, utilisez f.Read(buf) dans une boucle ou io.Copy vers un autre writer.

Écrire : os.Create, os.OpenFile et l'ajout en fin de fichier

os.Create(name) ouvre un fichier en écriture, en le créant ou en le tronquant. os.OpenFile donne un contrôle total grâce à des flags :

FlagSignification
os.O_RDONLY, os.O_WRONLY, os.O_RDWRouvrir en lecture, en écriture, ou les deux (choisissez-en un)
os.O_CREATEcréer le fichier s'il n'existe pas
os.O_TRUNCvider le fichier à l'ouverture
os.O_APPENDchaque écriture va à la fin
os.O_EXCLavec O_CREATE : échouer si le fichier existe déjà

os.Open(name) équivaut à OpenFile(name, O_RDONLY, 0). os.Create(name) équivaut à OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0o666).

En écriture, l'erreur de Close compte. Certains systèmes de fichiers ne signalent les échecs d'écriture qu'à la fermeture, donc defer f.Close() seul peut masquer une perte de données. Pour les fichiers que vous écrivez, vérifiez Close explicitement comme le fait appendLine. Pour les fichiers que vous lisez seulement, defer f.Close() suffit.

fmt.Fprintln et les autres fonctions d'affichage de fmt acceptent n'importe quel io.Writer, y compris un fichier. Un bufio.Writer regroupe les petites écritures en mémoire. Oublier w.Flush() est un bug classique : le programme se termine normalement et les derniers kilo-octets n'atteignent jamais le fichier.

Le fichier existe-t-il ?

Go n'a pas de os.Exists. Appelez os.Stat et examinez l'erreur :

errors.Is(err, fs.ErrNotExist) est l'idiome actuel. Il remplace l'ancien os.IsNotExist(err), qui ne voit pas à travers les erreurs enveloppées.

Vérifier avant d'ouvrir est souvent inutile et sujet aux races : le fichier peut apparaître ou disparaître entre la vérification et l'ouverture. En général, vous l'ouvrez simplement et gérez le fs.ErrNotExist renvoyé par l'ouverture. Pour créer un fichier seulement s'il n'existe pas encore, utilisez O_CREATE|O_EXCL, qui fait de la vérification et de la création une seule étape atomique.

Répertoires

TâcheFonction
créer un répertoireos.Mkdir(path, 0o755)
créer un chemin avec ses parentsos.MkdirAll(path, 0o755)
lister un répertoireos.ReadDir(path)
parcourir une arborescencefilepath.WalkDir(root, fn)
supprimer un fichier ou un répertoire videos.Remove(path)
supprimer une arborescenceos.RemoveAll(path)
renommer ou déplaceros.Rename(old, new)
fichier ou répertoire temporaireos.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix")
assembler des morceaux de cheminfilepath.Join(a, b, c)

Utilisez path/filepath pour les chemins du système de fichiers : il emploie le bon séparateur pour le système d'exploitation (\ sous Windows). Le package path sert aux chemins séparés par des barres obliques, comme les URL.

Go 1.24 a aussi ajouté os.Root (os.OpenRoot(dir)), qui n'ouvre des fichiers qu'à l'intérieur d'un répertoire et refuse les chemins qui en sortent via .. ou des liens symboliques. Utilisez-le quand les noms de fichiers viennent des utilisateurs.

Erreurs courantes

  • Ne pas vérifier les erreurs. Chacun de ces appels peut échouer. Un fichier dont l'ouverture a échoué vaut nil, et chaque Read, Write ou Close ultérieur renvoie invalid argument, ce qui masque la vraie cause (le fichier manquait, ou la permission a été refusée).
  • Oublier sc.Err() après une boucle de lecture. Une erreur de lecture ressemble à une fin de fichier.
  • Oublier Flush sur un bufio.Writer. La fin du fichier manque.
  • Ignorer l'erreur de Close après une écriture. Les erreurs d'écriture peuvent n'apparaître qu'à ce moment-là.
  • defer f.Close() dans une boucle sur de nombreux fichiers. Les fichiers restent ouverts jusqu'au retour de la fonction et vous pouvez manquer de descripteurs de fichiers. Déplacez le corps dans une fonction pour que chaque fichier soit fermé à chaque itération.
  • Des permissions écrites en décimal. 644 n'est pas 0o644. Go lit 644 comme le nombre décimal, soit 0o1204, ce qui positionne des bits étranges.

Questions fréquentes

Comment lire tout un fichier dans une chaîne en Go ?

data, err := os.ReadFile("notes.txt") renvoie le contenu sous forme de []byte ; convertissez avec string(data). La fonction ouvre, lit et ferme le fichier pour vous. Utilisez-la pour des fichiers qui tiennent confortablement en mémoire ; pour les gros fichiers, lisez ligne par ligne avec bufio.Scanner.

Comment lire un fichier ligne par ligne en Go ?

Ouvrez le fichier avec os.Open, faites defer f.Close(), enveloppez-le dans bufio.NewScanner(f), bouclez avec for sc.Scan() { line := sc.Text() }, et vérifiez sc.Err() après la boucle. Les lignes de plus de 64 Ko font échouer le scanner avec token too long, sauf si vous agrandissez son buffer avec sc.Buffer.

Comment ajouter du contenu à la fin d'un fichier en Go ?

Ouvrez-le avec os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644), écrivez, et vérifiez l'erreur de Close. O_CREATE crée le fichier s'il n'existe pas, et O_APPEND envoie chaque écriture à la fin.

Comment vérifier si un fichier existe en Go ?

Appelez os.Stat(path) et testez l'erreur avec errors.Is(err, fs.ErrNotExist). Une erreur nil signifie qu'il existe. Toute autre erreur (permission refusée, par exemple) signifie que vous ne pouvez pas savoir, donc traitez-la à part au lieu de la considérer comme « n'existe pas ».

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER