Skip to content

Fichiers

Les bibliothèques standard fournies par le langage Go pour le traitement de fichiers sont principalement les suivantes :

  • La bibliothèque os, responsable de l'implémentation concrète de l'interaction avec le système de fichiers du système d'exploitation
  • La bibliothèque io, couche d'abstraction pour la lecture et l'écriture des IO
  • La bibliothèque fs, couche d'abstraction pour le système de fichiers

Cet article explique comment effectuer un traitement de fichiers de base en Go.

Ouverture

Les deux façons courantes d'ouvrir un fichier consistent à utiliser les deux fonctions fournies par le package os. La fonction Open retourne un pointeur de fichier et une erreur :

go
func Open(name string) (*File, error)

La seconde OpenFile permet un contrôle plus fin. La fonction Open est simplement un encapsulage de la fonction OpenFile.

go
func OpenFile(name string, flag int, perm FileMode) (*File, error)

Commençons par présenter la première méthode d'utilisation, il suffit de fournir le nom de fichier correspondant, comme suit :

go
func main() {
   file, err := os.Open("README.txt")
   fmt.Println(file, err)
}

Le chemin de recherche du fichier est par défaut le chemin où se trouve le fichier go.mod du projet. Comme il n'y a pas de fichier README.txt dans le projet, une erreur est naturellement retournée.

<nil> open README.txt: The system cannot find the file specified.

Comme il existe de nombreux types d'erreurs IO, il est nécessaire de déterminer manuellement si le fichier existe. Le package os fournit également des fonctions utilitaires à cet effet. Le code modifié est le suivant :

go
func main() {
  file, err := os.Open("README.txt")
  if os.IsNotExist(err) {
    fmt.Println("Le fichier n'existe pas")
  } else if err != nil {
    fmt.Println("Accès au fichier anormal")
  } else {
    fmt.Println("Lecture du fichier réussie", file)
  }
}

L'exécution donne le résultat suivant :

Le fichier n'existe pas

En fait, la première fonction lit le fichier en lecture seule et ne peut pas être modifiée.

go
func Open(name string) (*File, error) {
  return OpenFile(name, O_RDONLY, 0)
}

La fonction OpenFile permet de contrôler plus de détails, tels que la modification du descripteur de fichier et des permissions de fichier. Concernant le descripteur de fichier, le package os fournit les constantes suivantes :

go
const (
   // Lecture seule, écriture seule, lecture-écriture : l'un des trois doit être spécifié
   O_RDONLY int = syscall.O_RDONLY // Ouvre le fichier en mode lecture seule
   O_WRONLY int = syscall.O_WRONLY // Ouvre le fichier en mode écriture seule
   O_RDWR   int = syscall.O_RDWR   // Ouvre le fichier en mode lecture-écriture
   // Les valeurs restantes sont utilisées pour contrôler le comportement
   O_APPEND int = syscall.O_APPEND // Lors de l'écriture dans le fichier, les données sont ajoutées à la fin du fichier
   O_CREATE int = syscall.O_CREAT  // Crée le fichier s'il n'existe pas
   O_EXCL   int = syscall.O_EXCL   // À utiliser avec O_CREATE, le fichier ne doit pas exister
   O_SYNC   int = syscall.O_SYNC   // Ouvre le fichier en mode IO synchrone
   O_TRUNC  int = syscall.O_TRUNC  // Tronque le fichier en écriture lors de l'ouverture
)

Concernant les permissions de fichier, les constantes suivantes sont fournies :

go
const (
   ModeDir        = fs.ModeDir        // d: répertoire
   ModeAppend     = fs.ModeAppend     // a: ajout uniquement
   ModeExclusive  = fs.ModeExclusive  // l: exclusif
   ModeTemporary  = fs.ModeTemporary  // T: fichier temporaire
   ModeSymlink    = fs.ModeSymlink    // L: lien symbolique
   ModeDevice     = fs.ModeDevice     // D: fichier périphérique
   ModeNamedPipe  = fs.ModeNamedPipe  // p: tube nommé (FIFO)
   ModeSocket     = fs.ModeSocket     // S: socket de domaine Unix
   ModeSetuid     = fs.ModeSetuid     // u: setuid
   ModeSetgid     = fs.ModeSetgid     // g: setgid
   ModeCharDevice = fs.ModeCharDevice // c: périphérique de caractères Unix, à condition que ModeDevice soit défini
   ModeSticky     = fs.ModeSticky     // t: bit sticky
   ModeIrregular  = fs.ModeIrregular  // ?: fichier irrégulier

   // Masque des bits de type. Pour les fichiers réguliers, rien n'est défini.
   ModeType = fs.ModeType

   ModePerm = fs.ModePerm // Bits de permission Unix, 0o777
)

Voici un exemple de code pour ouvrir un fichier en mode lecture-écriture, avec des permissions 0666, ce qui signifie que tout le monde peut lire et écrire ce fichier, et qu'il sera automatiquement créé s'il n'existe pas.

go
func main() {
  file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
  if os.IsNotExist(err) {
    fmt.Println("Le fichier n'existe pas")
  } else if err != nil {
    fmt.Println("Accès au fichier anormal")
  } else {
    fmt.Println("Ouverture du fichier réussie", file.Name())
    file.Close()
  }
}

Le résultat est le suivant :

Ouverture du fichier réussie README.txt

Si vous souhaitez simplement obtenir des informations sur le fichier sans le lire, vous pouvez utiliser la fonction os.Stat(), comme suit :

go
func main() {
  fileInfo, err := os.Stat("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(fmt.Sprintf("%+v", fileInfo))
  }
}

Le résultat est le suivant :

&{name:README.txt FileAttributes:32 CreationTime:{LowDateTime:3603459389 HighDateTime:31016791} LastAccessTime:{LowDateTime:3603459389 HighDateTime:31016791} LastWriteTime:{LowDateTime:3603459389 HighDateTime:31016791} FileSizeHigh:0 FileSizeLow:0 Reserved0:0 filetype:0 Mutex:{state:0 sema:0} path:README.txt vol:0 idxhi:0 idxlo:0 appendNameToPath:false}

WARNING

Après avoir ouvert un fichier, n'oubliez jamais de le fermer. L'opération de fermeture est généralement placée dans une instruction defer.

go
defer file.Close()

Lecture

Une fois le fichier ouvert avec succès, vous pouvez procéder à l'opération de lecture. Concernant les opérations de lecture de fichier, le type *os.File fournit les méthodes publiques suivantes :

go
// Lit le fichier dans le slice d'octets transmis
func (f *File) Read(b []byte) (n int, err error)

// Par rapport à la première, peut lire à partir d'un décalage spécifié
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

Dans la plupart des cas, la première méthode est plus couramment utilisée. Pour la première méthode, il est nécessaire d'écrire soi-même la logique pour l'expansion dynamique du slice lors de la lecture, comme suit :

go
func ReadFile(file *os.File) ([]byte, error) {
  buffer := make([]byte, 0, 512)
  for {
    // Lorsque la capacité est insuffisante
    if len(buffer) == cap(buffer) {
      // Expansion
      buffer = append(buffer, 0)[:len(buffer)]
    }
    // Continuer à lire le fichier
    offset, err := file.Read(buffer[len(buffer):cap(buffer)])
    // Inclure les données écrites dans le slice
    buffer = buffer[:len(buffer)+offset]
    // En cas d'erreur
    if err != nil {
      if errors.Is(err, io.EOF) {
        err = nil
      }
      return buffer, err
    }
  }
}

Le reste de la logique est le suivant :

go
func main() {
   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Accès au fichier anormal")
   } else {
      fmt.Println("Ouverture du fichier réussie", file.Name())
      bytes, err := ReadFile(file)
      if err != nil {
         fmt.Println("Lecture du fichier anormale", err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}

Le résultat est :

Ouverture du fichier réussie README.txt
hello world!

En outre, vous pouvez utiliser deux fonctions utilitaires pour lire le fichier, à savoir la fonction ReadFile du package os et la fonction ReadAll du package io. Pour os.ReadFile, il suffit de fournir le chemin du fichier, tandis que pour io.ReadAll, il est nécessaire de fournir une implémentation du type io.Reader.

os.ReadFile

go
func ReadFile(name string) ([]byte, error)

Exemple d'utilisation :

go
func main() {
  bytes, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(string(bytes))
  }
}

Le résultat est le suivant :

hello world!

io.ReadAll

go
func ReadAll(r Reader) ([]byte, error)

Exemple d'utilisation :

go
func main() {

   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("Accès au fichier anormal")
   } else {
      fmt.Println("Ouverture du fichier réussie", file.Name())
      bytes, err := io.ReadAll(file)
      if err != nil {
         fmt.Println(err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}
Ouverture du fichier réussie README.txt
hello world!

Écriture

La structure os.File fournit les méthodes suivantes pour écrire des données :

go
// Écrit un slice d'octets
func (f *File) Write(b []byte) (n int, err error)

// Écrit une chaîne
func (f *File) WriteString(s string) (n int, err error)

// Écrit à partir d'une position spécifiée, retourne une erreur lorsqu'ouvert en mode os.O_APPEND
func (f *File) WriteAt(b []byte, off int64) (n int, err error)

Si vous souhaitez écrire des données dans un fichier, vous devez l'ouvrir en mode O_WRONLY ou O_RDWR, sinon l'écriture échouera. Voici un exemple d'ouverture d'un fichier en mode os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC avec des permissions 0666 pour écrire des données :

go
func main() {
  file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
  if err != nil {
    fmt.Println("Accès au fichier anormal")
  } else {
    fmt.Println("Ouverture du fichier réussie", file.Name())
    for i := 0; i < 5; i++ {
      offset, err := file.WriteString("hello world!\n")
      if err != nil {
        fmt.Println(offset, err)
      }
    }
    fmt.Println(file.Close())
  }
}

Comme le fichier est ouvert en mode os.O_APPEND, les données seront ajoutées à la fin du fichier lors de l'écriture. Après exécution, le contenu du fichier est le suivant :

txt
hello world!
hello world!
hello world!
hello world!
hello world!

L'écriture d'un slice d'octets dans le fichier est une opération similaire, nous ne la détaillerons pas davantage. Pour les opérations d'écriture dans un fichier, la bibliothèque standard fournit également des fonctions utilitaires, à savoir os.WriteFile et io.WriteString.

os.WriteFile

go
func WriteFile(name string, data []byte, perm FileMode) error

Exemple d'utilisation :

go
func main() {
  err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666)
  if err != nil {
    fmt.Println(err)
  }
}

Le contenu du fichier est alors le suivant :

txt
hello world!

io.WriteString

go
func WriteString(w Writer, s string) (n int, err error)

Exemple d'utilisation :

go
func main() {
   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
   if err != nil {
      fmt.Println("Accès au fichier anormal")
   } else {
      fmt.Println("Ouverture du fichier réussie", file.Name())
      for i := 0; i < 5; i++ {
         offset, err := io.WriteString(file, "hello world!\n")
         if err != nil {
            fmt.Println(offset, err)
         }
      }
      fmt.Println(file.Close())
   }
}
hello world!
hello world!
hello world!
hello world!
hello world!

La fonction os.Create est utilisée pour créer un fichier. Elle est essentiellement également un encapsulage de OpenFile.

go
func Create(name string) (*File, error) {
   return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

WARNING

Lors de la création d'un fichier, si le répertoire parent n'existe pas, la création échouera et une erreur sera retournée.

Copie

Pour copier un fichier, il est nécessaire d'ouvrir deux fichiers simultanément. La première méthode consiste à lire les données du fichier d'origine, puis à les écrire dans le fichier cible, comme suit :

go
func main() {
    // Lit les données du fichier d'origine
  data, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
    return
  }
    // Écrit dans le fichier cible
  err = os.WriteFile("README(1).txt", data, 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Copie réussie")
  }
}

*os.File.ReadFrom

Une autre méthode consiste à utiliser la méthode ReadFrom fournie par os.File. Lors de l'ouverture des fichiers, l'un en lecture seule et l'autre en écriture seule.

go
func (f *File) ReadFrom(r io.Reader) (n int64, err error)

Exemple d'utilisation :

go
func main() {
  // Ouvre le fichier d'origine en lecture seule
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Ouvre le fichier de copie en écriture seule
  target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer target.Close()
  // Lit les données du fichier d'origine, puis les écrit dans le fichier de copie
  offset, err := target.ReadFrom(origin)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println("Copie du fichier réussie", offset)
}

Cette méthode de copie nécessite de lire d'abord tout le contenu du fichier source en mémoire, puis de l'écrire dans le fichier cible. Il est déconseillé de procéder ainsi lorsque le fichier est particulièrement volumineux.

io.Copy

Une autre méthode consiste à utiliser la fonction io.Copy, qui lit et écrit simultanément, en lisant d'abord le contenu dans un tampon, puis en l'écrivant dans le fichier cible. La taille par défaut du tampon est de 32 Ko.

go
func Copy(dst Writer, src Reader) (written int64, err error)

Exemple d'utilisation :

go
func main() {
  // Ouvre le fichier d'origine en lecture seule
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // Ouvre le fichier de copie en écriture seule
  target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer target.Close()
  // Copie
  written, err := io.Copy(target, origin)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(written)
  }
}

Vous pouvez également utiliser io.CopyBuffer pour spécifier la taille du tampon.

Renommage

Le renommage peut également être compris comme le déplacement d'un fichier. La fonction Rename du package os est utilisée.

go
func Rename(oldpath, newpath string) error

Exemple :

go
func main() {
  err := os.Rename("README.txt", "readme.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Renommage réussi")
  }
}

Cette fonction a le même effet pour les répertoires.

Suppression

L'opération de suppression est beaucoup plus simple que les autres opérations. Seules deux fonctions du package os sont utilisées :

go
// Supprime un fichier unique ou un répertoire vide, retourne une erreur lorsque le répertoire n'est pas vide
func Remove(name string) error

// Supprime tous les fichiers et répertoires du répertoire spécifié, y compris les sous-répertoires et les sous-fichiers
func RemoveAll(path string) error

L'utilisation est très simple. Voici un exemple de suppression d'un répertoire :

go
func main() {
  // Supprime tous les fichiers et sous-répertoires du répertoire actuel
  err := os.RemoveAll(".")
  if err != nil {
    fmt.Println(err)
  }else {
    fmt.Println("Suppression réussie")
  }
}

Voici un exemple de suppression d'un fichier unique :

go
func main() {
  // Supprime le fichier README.txt du répertoire actuel
  err := os.Remove("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Suppression réussie")
  }
}

Synchronisation

La fonction os.Sync encapsule l'appel système sous-jacent Fsync, utilisé pour écrire les IO mises en cache dans le système d'exploitation sur le disque.

go
func main() {
  create, err := os.Create("test.txt")
  if err != nil {
    panic(err)
  }
  defer create.Close()

  _, err = create.Write([]byte("hello"))
  if err != nil {
    panic(err)
  }

    // Synchronisation sur disque
  if err := create.Sync();err != nil {
    return
  }
}

Répertoires

De nombreuses opérations sur les répertoires sont similaires aux opérations sur les fichiers.

Lecture

Pour les répertoires, il existe deux façons de les ouvrir.

os.ReadDir

La première méthode consiste à utiliser la fonction os.ReadDir :

go
func ReadDir(name string) ([]DirEntry, error)
go
func main() {
   // Répertoire actuel
   dir, err := os.ReadDir(".")
   if err != nil {
      fmt.Println(err)
   } else {
      for _, entry := range dir {
         fmt.Println(entry.Name())
      }
   }
}

*os.File.ReadDir

La deuxième méthode consiste à utiliser la fonction *os.File.ReadDir. os.ReadDir n'est essentiellement qu'un simple encapsulage de *os.File.ReadDir.

go
// Lorsque n < 0, lit tout le contenu du répertoire
func (f *File) ReadDir(n int) ([]DirEntry, error)
go
func main() {
   // Répertoire actuel
   dir, err := os.Open(".")
   if err != nil {
      fmt.Println(err)
   }
   defer dir.Close()
   dirs, err := dir.ReadDir(-1)
   if err != nil {
      fmt.Println(err)
   } else {
      for _, entry := range dirs {
         fmt.Println(entry.Name())
      }
   }
}

Création

La création d'un répertoire utilise deux fonctions du package os :

go
// Crée un répertoire avec le nom spécifié et les permissions spécifiées
func Mkdir(name string, perm FileMode) error

// Par rapport à la précédente, cette fonction crée tous les répertoires parents nécessaires
func MkdirAll(path string, perm FileMode) error

Exemple :

go
func main() {
  err := os.Mkdir("src", 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("Création réussie")
  }
}

Copie

Nous pouvons écrire nous-mêmes une fonction pour parcourir récursivement tout le répertoire, mais la bibliothèque standard filepath fournit déjà des fonctions similaires, nous pouvons donc les utiliser directement. Voici un exemple simple de copie d'un répertoire :

go
func CopyDir(src, dst string) error {
    // Vérifie l'état du répertoire source
  _, err := os.Stat(src)
  if err != nil {
    return err
  }

  return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
    if err != nil {
      return err
    }

        // Calcule le chemin relatif
    rel, err := filepath.Rel(src, path)
    if err != nil {
      return err
    }

        // Concatène le chemin de destination
    destpath := filepath.Join(dst, rel)

        // Crée le répertoire
    var dirpath string
    var mode os.FileMode = 0755
    if info.IsDir() {
      dirpath = destpath
      mode = info.Mode()
    } else if info.Mode().IsRegular() {
      dirpath = filepath.Dir(destpath)
    }

    if err := os.MkdirAll(dirpath, mode); err != nil {
      return err
    }

        // Crée le fichier
    if info.Mode().IsRegular() {
      srcfile, err := os.Open(path)
      if err != nil {
        return err
      }
            // N'oubliez pas de fermer le fichier
      defer srcfile.Close()
      destfile, err := os.OpenFile(destpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
      if err != nil {
        return err
      }
      defer destfile.Close()

            // Copie le contenu du fichier
      if _, err := io.Copy(destfile, srcfile); err != nil {
        return err
      }
      return nil
    }

    return nil
  })
}

filepath.Walk parcourt récursivement tout le répertoire. Pendant le processus, il crée des répertoires lorsqu'il rencontre des répertoires, et crée de nouveaux fichiers et les copie lorsqu'il rencontre des fichiers. Le code est un peu plus long que la copie de fichiers, mais n'est pas complexe.

Golang by www.golangdev.cn edit