Come leggere / scrivere da / su file usando Go?


284

Ho cercato di imparare Go per conto mio, ma sono rimasto sconcertato nel provare a leggere e scrivere su file ordinari.

Posso arrivare fino a inFile, _ := os.Open(INFILE, 0, 0), ma effettivamente ottenere il contenuto del file non ha senso, perché la funzione di lettura accetta []byteun parametro.

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

Risposte:


476

Facciamo un elenco compatibile con Go 1 di tutti i modi per leggere e scrivere file in Go.

Poiché l'API dei file è cambiata di recente e la maggior parte delle altre risposte non funziona con Go 1. Hanno anche perso l' bufioIMHO importante.

Negli esempi seguenti copio un file leggendo da esso e scrivendo nel file di destinazione.

Inizia con le basi

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)
        }
    }
}

Qui ho usato os.Opene os.Createquali sono i wrapper convenienti in giro os.OpenFile. Di solito non abbiamo bisogno di chiamare OpenFiledirettamente.

Si noti il ​​trattamento di EOF. Readtenta di riempire bufogni chiamata e restituisce io.EOFcome errore se raggiunge la fine del file. In questo caso bufmanterrà comunque i dati. Le chiamate successive a Readrestituiscono zero come numero di byte letti e uguale io.EOFa errore. Qualsiasi altro errore porterà ad un panico.

utilizzando 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)
    }
}

bufioagisce solo come buffer qui, perché non abbiamo molto a che fare con i dati. Nella maggior parte delle altre situazioni (specialmente con file di testo) bufioè molto utile dandoci una bella API per leggere e scrivere facilmente e in modo flessibile, mentre gestisce il buffering dietro le quinte.

utilizzando 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)
    }
}

Facile come una torta! Ma usalo solo se sei sicuro di non avere a che fare con file di grandi dimensioni.


55
Per chiunque si imbattesse in questa domanda, è stata originariamente posta nel 2009 prima dell'introduzione di queste librerie, quindi per favore, usa questa risposta come guida!
Seth Hoenig,

1
Secondo golang.org/pkg/os/#File.Write , quando Write non ha scritto tutti i byte, restituisce un errore. Quindi il controllo extra nel primo esempio ( panic("error in writing")) non è necessario.
Ayke

15
Si noti che questi esempi non stanno verificando il ritorno dell'errore da fo.Close (). Dalle pagine man di Linux close (2): Non verificare il valore restituito da close () è un errore di programmazione comune ma comunque grave. È del tutto possibile che gli errori di una precedente operazione di scrittura (2) vengano segnalati per la prima volta alla chiusura () finale. La mancata verifica del valore restituito alla chiusura del file può comportare la perdita silenziosa dei dati. Ciò può essere osservato in particolare con NFS e con la quota del disco.
Nick Craig-Wood,

12
Quindi, cos'è un file "grande"? 1KB? 1MB? 1 GB? O "grande" dipende dall'hardware della macchina?
425nesp,

3
@ 425nesp Legge l'intero file nella memoria, quindi dipende dalla quantità di memoria disponibile nella macchina in esecuzione.
Mostafa,

50

Questa è una buona versione:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}

8
Questo memorizza l'intero file in memoria. Poiché il file può essere di grandi dimensioni, potrebbe non essere sempre quello che vuoi fare.
user7610

9
Inoltre, 0x777è falso. In ogni caso, dovrebbe essere più simile 0644o 0755(ottale, non esadecimale).
primo

@cnst l'ha cambiato in 0644 da 0x777
Trenton l'

31

utilizzando 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)
}

Se non hai voglia di reinventare la ruota, il io.Copye io.CopyNpotrebbe servirti bene. Se si controlla l'origine della funzione io.Copy, non è altro che una delle soluzioni di Mostafa (quella "di base", in realtà) impacchettata nella libreria Go. Stanno usando un buffer significativamente più grande di lui, però.


5
una cosa degna di nota - per essere sicuri che il contenuto del file sia stato scritto su disco, è necessario utilizzarlo w.Sync()dopoio.Copy(w, r)
Shay Tsadok,

Inoltre, se si scrive in un file già esistente, io.Copy()verranno scritti solo i dati con cui lo si alimenta, quindi se il file esistente ha più contenuto, non verrà rimosso, il che potrebbe causare un file danneggiato.
Invidian,

1
@Invidian Tutto dipende da come si apre il file di destinazione. Se lo fai w, err := os.Create("output.txt"), ciò che descrivi non accade perché "Crea crea o tronca il file indicato. Se il file esiste già, viene troncato". golang.org/pkg/os/#Create .
user7610

Questa dovrebbe essere la risposta corretta in quanto non reinventa la ruota senza dover leggere l'intero file contemporaneamente prima di leggerlo.
Eli Davis

11

Con le versioni Go più recenti, leggere / scrivere su / da file è facile. Per leggere da un file:

package main

import (
    "fmt"
    "io/ioutil"
)

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

Per scrivere su un file:

package main

import "os"

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

    file.WriteString("test\nhello")
}

Questo sovrascriverà il contenuto di un file (crea un nuovo file se non fosse presente).


10

[]byteè una sezione (simile a una sottostringa) di tutto o parte di un array di byte. Pensa alla sezione come una struttura di valori con un campo puntatore nascosto per consentire al sistema di individuare e accedere a tutto o parte di un array (la sezione), oltre ai campi per la lunghezza e la capacità della sezione, a cui puoi accedere utilizzando le funzioni len()e cap().

Ecco uno starter kit funzionante per te, che legge e stampa un file binario; dovrai cambiare il inNamevalore letterale per fare riferimento a un piccolo file sul tuo sistema.

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();
}

9
La convenzione Go è di verificare prima l'errore e lasciare che il codice normale risieda all'esterno del ifblocco
hasen

@Jurily: se il file è aperto quando si verifica l'errore, come lo si chiude?
peterSO,

10
@peterSO: usa il differimento
James Antill il

Ma perché un byte [256] non è accettato e il chiaramente sciocco e dettagliato (ma apparentemente non sbagliato) inBuf: = make ([] byte, 256) accettato?
Cardiff Space Man,

7

Prova questo:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}

1
Questo funzionerà se vuoi leggere l'intero file in una volta. Se il file è davvero grande o vuoi solo leggerne una parte, potrebbe non essere quello che stai cercando.
Evan Shaw,

3
Dovresti davvero controllare il codice di errore e non ignorarlo in quel modo !!
hasen

7
Questo è stato spostato nel pacchetto ioutil ora. Quindi sarebbe ioutil.ReadFile ()
Christopher

Ho risolto così dice 0644
Joakim il

1

Solo guardando la documentazione sembra che dovresti semplicemente dichiarare un buffer di tipo [] byte e passarlo alla lettura che leggerà fino a quel numero di caratteri e restituirà il numero di caratteri effettivamente letti (e un errore).

I documenti dicono

Leggi legge fino a len (b) byte dal file. Restituisce il numero di byte letti e un eventuale Errore. EOF è segnalato da un conteggio zero con err impostato su EOF.

Non funziona?

EDIT: Inoltre, penso che dovresti forse usare le interfacce Reader / Writer dichiarate nel pacchetto bufio invece di usare il pacchetto os .


Hai il mio voto perché riconosci effettivamente ciò che le persone reali vedono quando leggono la documentazione, invece di pappagallo di ciò a cui sono abituati coloro che sono abituati a andare (non leggono RICORDATI) quando leggono la documentazione della funzione con cui hanno già familiarità.
cardiff space man

1

Il metodo Read accetta un parametro byte perché è il buffer in cui leggerà. È un linguaggio comune in alcuni ambienti e ha un senso quando ci pensi.

In questo modo è possibile determinare quanti byte verranno letti dal lettore e ispezionare il ritorno per vedere quanti byte sono stati effettivamente letti e gestire gli errori in modo appropriato.

Come altri hanno sottolineato nelle loro risposte, bufio è probabilmente quello che vuoi per leggere dalla maggior parte dei file.

Aggiungerò un altro suggerimento poiché è davvero utile. La lettura di una riga da un file si ottiene meglio non con il metodo ReadLine ma con il metodo ReadBytes o ReadString.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.