Comment lire / écrire depuis / vers un fichier en utilisant Go?

284

J'ai essayé d'apprendre Go par moi-même, mais j'ai eu du mal à essayer de lire et d'écrire dans des fichiers ordinaires.

Je peux aller aussi loin inFile, _ := os.Open(INFILE, 0, 0), mais en fait, obtenir le contenu du fichier n'a pas de sens, car la fonction de lecture prend []byteun paramètre.

func (file *File) Read(b []byte) (n int, err Error)
Seth Hoenig
la source

Réponses:

476

Faisons une liste compatible Go 1 de toutes les façons de lire et d'écrire des fichiers dans Go.

Parce que l'API de fichiers a changé récemment et la plupart des autres réponses ne fonctionnent pas avec Go 1. Ils manquent également bufioce qui est important à mon humble avis.

Dans les exemples suivants, je copie un fichier en le lisant et en l'écrivant dans le fichier de destination.

Commencez avec les bases

package main

import (
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := fi.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := fo.Write(buf[:n]); err != nil {
            panic(err)
        }
    }
}

Ici, j'ai utilisé os.Openet os.Createqui sont des emballages pratiques autour os.OpenFile. Nous n'avons généralement pas besoin d'appeler OpenFiledirectement.

Remarquez le traitement de l'EOF. Readessaie de répondre bufà chaque appel et retourne io.EOFcomme erreur s'il atteint la fin du fichier. Dans ce cas buf, conservera toujours les données. Les appels ultérieurs à Readrenvoie zéro comme le nombre d'octets lus et identique io.EOFà l'erreur. Toute autre erreur entraînera une panique.

En utilisant bufio

package main

import (
    "bufio"
    "io"
    "os"
)

func main() {
    // open input file
    fi, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    // close fi on exit and check for its returned error
    defer func() {
        if err := fi.Close(); err != nil {
            panic(err)
        }
    }()
    // make a read buffer
    r := bufio.NewReader(fi)

    // open output file
    fo, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    // close fo on exit and check for its returned error
    defer func() {
        if err := fo.Close(); err != nil {
            panic(err)
        }
    }()
    // make a write buffer
    w := bufio.NewWriter(fo)

    // make a buffer to keep chunks that are read
    buf := make([]byte, 1024)
    for {
        // read a chunk
        n, err := r.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        // write a chunk
        if _, err := w.Write(buf[:n]); err != nil {
            panic(err)
        }
    }

    if err = w.Flush(); err != nil {
        panic(err)
    }
}

bufioagit simplement comme un tampon ici, car nous n'avons pas grand-chose à voir avec les données. Dans la plupart des autres situations (spécialement avec les fichiers texte), bufioc'est très utile en nous donnant une belle API pour lire et écrire facilement et de manière flexible, tout en gérant la mise en mémoire tampon en arrière-plan.

En utilisant ioutil

package main

import (
    "io/ioutil"
)

func main() {
    // read the whole file at once
    b, err := ioutil.ReadFile("input.txt")
    if err != nil {
        panic(err)
    }

    // write the whole body at once
    err = ioutil.WriteFile("output.txt", b, 0644)
    if err != nil {
        panic(err)
    }
}

C'est de la tarte! Mais utilisez-le uniquement si vous êtes sûr que vous ne traitez pas avec de gros fichiers.

Mostafa
la source
55
Pour tous ceux qui tombent sur cette question, elle a été posée à l'origine en 2009 avant l'introduction de ces bibliothèques, alors s'il vous plaît, utilisez cette réponse comme guide!
Seth Hoenig
1
Selon golang.org/pkg/os/#File.Write , lorsque Write n'a pas écrit tous les octets, il renvoie une erreur. La vérification supplémentaire dans le premier exemple ( panic("error in writing")) n'est donc pas nécessaire.
ayke
15
Notez que ces exemples ne vérifient pas le retour d'erreur de fo.Close (). Depuis les pages de manuel Linux close (2): Ne pas vérifier la valeur de retour de close () est une erreur de programmation courante mais néanmoins sérieuse. Il est fort possible que des erreurs sur une opération d'écriture précédente (2) soient d'abord signalées à la fermeture finale (). Ne pas vérifier la valeur de retour lors de la fermeture du fichier peut entraîner une perte silencieuse de données. Cela peut notamment être observé avec NFS et avec le quota de disque.
Nick Craig-Wood
12
Alors, qu'est-ce qu'un "gros" fichier? 1 Ko? 1 Mo? 1 Go? Ou "grand" dépend-il du matériel de la machine?
2014 à 42 h 27 min
3
@ 425nesp Il lit l'intégralité du fichier dans la mémoire, cela dépend donc de la quantité de mémoire disponible sur la machine en cours d'exécution.
Mostafa
50

C'est une bonne version:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}
Piotr
la source
8
Cela stocke tout le fichier en mémoire. Étant donné que le fichier peut être volumineux, ce n'est peut-être pas toujours ce que vous voulez faire.
user7610
9
En outre, 0x777est faux. Dans tous les cas, il devrait ressembler davantage à 0644ou 0755(octal, pas hex).
2014 à 15h56
@cnst l'a changé en 0644 de 0x777
Trenton
31

En utilisant io.Copy

package main

import (
    "io"
    "log"
    "os"
)

func main () {
    // open files r and w
    r, err := os.Open("input.txt")
    if err != nil {
        panic(err)
    }
    defer r.Close()

    w, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    defer w.Close()

    // do the actual work
    n, err := io.Copy(w, r)
    if err != nil {
        panic(err)
    }
    log.Printf("Copied %v bytes\n", n)
}

Si vous n'avez pas envie de réinventer la roue, le io.Copyet io.CopyNpeut bien vous servir. Si vous vérifiez la source de la fonction io.Copy, ce n'est rien d'autre qu'une des solutions de Mostafa (la «basique», en fait) packagée dans la bibliothèque Go. Cependant, ils utilisent un tampon beaucoup plus grand que lui.

user7610
la source
5
une chose à mentionner - pour être sûr que le contenu du fichier a été écrit sur le disque, vous devez l'utiliser w.Sync()après leio.Copy(w, r)
Shay Tsadok
De plus, si vous écrivez dans un fichier déjà existant, io.Copy()n'écrira que les données avec lesquelles vous l'alimentez, donc si le fichier existant avait plus de contenu, il ne sera pas supprimé, ce qui peut entraîner la corruption du fichier.
Invidian
1
@Invidian Tout dépend de la façon dont vous ouvrez le fichier de destination. Si vous le faites w, err := os.Create("output.txt"), ce que vous décrivez ne se produit pas, car «Créer crée ou tronque le fichier nommé. Si le fichier existe déjà, il est tronqué». golang.org/pkg/os/#Create .
user7610
Cela devrait être la bonne réponse car elle ne réinvente pas la roue sans avoir à lire le fichier entier à la fois avant de le lire.
Eli Davis
11

Avec les nouvelles versions de Go, la lecture / écriture vers / depuis un fichier est facile. Pour lire à partir d'un fichier:

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    data, err := ioutil.ReadFile("text.txt")
    if err != nil {
        return
    }
    fmt.Println(string(data))
}

Pour écrire dans un fichier:

package main

import "os"

func main() {
    file, err := os.Create("text.txt")
    if err != nil {
        return
    }
    defer file.Close()

    file.WriteString("test\nhello")
}

Cela écrasera le contenu d'un fichier (créez un nouveau fichier s'il n'y était pas).

Salvador Dali
la source
10

[]byteest une tranche (similaire à une sous-chaîne) de tout ou partie d'un tableau d'octets. Considérez la tranche comme une structure de valeurs avec un champ de pointeur caché pour que le système puisse localiser et accéder à tout ou partie d'un tableau (la tranche), plus des champs pour la longueur et la capacité de la tranche, auxquels vous pouvez accéder à l'aide des fonctions len()et cap().

Voici un kit de démarrage fonctionnel pour vous, qui lit et imprime un fichier binaire; vous devrez modifier la inNamevaleur littérale pour faire référence à un petit fichier sur votre système.

package main
import (
    "fmt";
    "os";
)
func main()
{
    inName := "file-rw.bin";
    inPerm :=  0666;
    inFile, inErr := os.Open(inName, os.O_RDONLY, inPerm);
    if inErr == nil {
        inBufLen := 16;
        inBuf := make([]byte, inBufLen);
        n, inErr := inFile.Read(inBuf);
        for inErr == nil {
            fmt.Println(n, inBuf[0:n]);
            n, inErr = inFile.Read(inBuf);
        }
    }
    inErr = inFile.Close();
}
peterSO
la source
9
La convention Go consiste à vérifier d'abord les erreurs et à laisser le code normal résider en dehors du ifbloc
hasen
@Jurily: Si le fichier est ouvert lorsque l'erreur se produit, comment le fermez-vous?
peterSO
10
@peterSO: use defer
James Antill
Mais pourquoi un [256] octet n'est-il pas accepté et l'acceptable clairement idiot et verbeux (mais apparemment pas faux) de Buf: = make ([], 256) est-il accepté?
Cardiff Space Man
7

Essaye ça:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}
commercialisation
la source
1
Cela fonctionnera si vous souhaitez lire l'intégralité du fichier en même temps. Si le fichier est vraiment volumineux ou si vous ne voulez en lire qu'une partie, ce n'est peut-être pas ce que vous recherchez.
Evan Shaw
3
Vous devriez vraiment vérifier le code d'erreur, et ne pas l'ignorer comme ça !!
hasen
7
Cela a été déplacé dans le package ioutil maintenant. Ce serait donc ioutil.ReadFile ()
Christopher
J'ai corrigé donc il est dit 0644
Joakim
1

En regardant simplement la documentation, il semble que vous devez simplement déclarer un tampon de type [] octet et le passer à read qui lira ensuite autant de caractères et retournera le nombre de caractères réellement lus (et une erreur).

Les docs disent

Read lit jusqu'à len (b) octets du fichier. Il renvoie le nombre d'octets lus et une erreur, le cas échéant. EOF est signalé par un comptage nul avec err réglé sur EOF.

Ça ne marche pas?

EDIT: Aussi, je pense que vous devriez peut-être utiliser les interfaces Reader / Writer déclarées dans le paquet bufio au lieu d'utiliser le paquet os .

Hannes Ovrén
la source
Vous avez mon vote parce que vous reconnaissez réellement ce que les vraies personnes voient quand elles lisent la documentation, au lieu de déchiffrer ce qui est RAPPELÉ à ceux qui sont habitués à aller (ne pas lire RAPPELÉ) lorsqu'ils lisent la documentation de la fonction qu'ils connaissent déjà.
Space Man à Cardiff du
1

La méthode Read prend un paramètre d'octet car c'est le tampon dans lequel il lira. C'est un idiome courant dans certains cercles et a un sens lorsque vous y pensez.

De cette façon, vous pouvez déterminer combien d'octets seront lus par le lecteur et inspecter le retour pour voir combien d'octets ont réellement été lus et gérer les erreurs de manière appropriée.

Comme d'autres l'ont souligné dans leurs réponses, bufio est probablement ce que vous voulez pour lire dans la plupart des fichiers.

J'ajouterai un autre indice car c'est vraiment utile. Il est préférable de lire une ligne à partir d'un fichier non pas par la méthode ReadLine mais par la méthode ReadBytes ou ReadString à la place.

Jeremy Wall
la source