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 :
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.
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 :
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 :
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 pasEn fait, la première fonction lit le fichier en lecture seule et ne peut pas être modifiée.
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 :
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 :
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.
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.txtSi vous souhaitez simplement obtenir des informations sur le fichier sans le lire, vous pouvez utiliser la fonction os.Stat(), comme suit :
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.
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 :
// 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 :
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 :
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
func ReadFile(name string) ([]byte, error)Exemple d'utilisation :
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
func ReadAll(r Reader) ([]byte, error)Exemple d'utilisation :
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 :
// É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 :
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 :
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
func WriteFile(name string, data []byte, perm FileMode) errorExemple d'utilisation :
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 :
hello world!io.WriteString
func WriteString(w Writer, s string) (n int, err error)Exemple d'utilisation :
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.
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 :
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.
func (f *File) ReadFrom(r io.Reader) (n int64, err error)Exemple d'utilisation :
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.
func Copy(dst Writer, src Reader) (written int64, err error)Exemple d'utilisation :
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.
func Rename(oldpath, newpath string) errorExemple :
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 :
// 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) errorL'utilisation est très simple. Voici un exemple de suppression d'un répertoire :
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 :
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.
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 :
func ReadDir(name string) ([]DirEntry, error)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.
// Lorsque n < 0, lit tout le contenu du répertoire
func (f *File) ReadDir(n int) ([]DirEntry, error)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 :
// 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) errorExemple :
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 :
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.
