Wie kann ich mit Go aus einer Datei lesen / schreiben?

284

Ich habe versucht, Go alleine zu lernen, aber ich war ratlos beim Versuch, aus normalen Dateien zu lesen und in diese zu schreiben.

Ich kann so weit kommen inFile, _ := os.Open(INFILE, 0, 0), aber tatsächlich macht es keinen Sinn, den Inhalt der Datei zu erhalten, da die Lesefunktion a []byteals Parameter verwendet.

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

Antworten:

476

Lassen Sie uns eine Go 1-kompatible Liste aller Möglichkeiten zum Lesen und Schreiben von Dateien in Go erstellen.

Da sich die Datei-API in letzter Zeit geändert hat und die meisten anderen Antworten mit Go 1 nicht funktionieren, fehlt ihnen auch, bufiowas meiner Meinung nach wichtig ist.

In den folgenden Beispielen kopiere ich eine Datei, indem ich daraus lese und in die Zieldatei schreibe.

Beginnen Sie mit den Grundlagen

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

Hier habe ich verwendet os.Openund os.Createwelche sind praktische Wrapper herum os.OpenFile. Wir müssen normalerweise nicht OpenFiledirekt anrufen .

Beachten Sie die Behandlung von EOF. Readversucht bufbei jedem Aufruf zu füllen und gibt io.EOFals Fehler zurück, wenn dabei das Dateiende erreicht wird. In diesem Fall bufwerden weiterhin Daten gespeichert. Nachfolgende Aufrufe von geben ReadNull als Anzahl der gelesenen Bytes und io.EOFals Fehler zurück. Jeder andere Fehler führt zu einer Panik.

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

bufiofungiert hier nur als Puffer, weil wir nicht viel mit Daten zu tun haben. In den meisten anderen Situationen (insbesondere bei Textdateien) bufioist dies sehr nützlich, da wir eine schöne API zum einfachen und flexiblen Lesen und Schreiben haben, während die Pufferung hinter den Kulissen erfolgt.

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

Einfach wie Torte! Verwenden Sie es jedoch nur, wenn Sie sicher sind, dass Sie nicht mit großen Dateien zu tun haben.

Mostafa
quelle
55
Für alle, die auf diese Frage stoßen, wurde sie ursprünglich im Jahr 2009 gestellt, bevor diese Bibliotheken eingeführt wurden. Verwenden Sie diese Antwort daher bitte als Leitfaden!
Seth Hoenig
1
Laut golang.org/pkg/os/#File.Write wird ein Fehler zurückgegeben , wenn Write nicht alle Bytes geschrieben hat. Die zusätzliche Prüfung im ersten Beispiel ( panic("error in writing")) ist also nicht erforderlich.
Ayke
15
Beachten Sie, dass diese Beispiele die Fehlerrückgabe von fo.Close () nicht überprüfen. Aus den Linux-Manpages close (2): Das Nicht-Überprüfen des Rückgabewerts von close () ist ein häufiger, aber dennoch schwerwiegender Programmierfehler. Es ist durchaus möglich, dass Fehler bei einer vorherigen Schreiboperation (2) zuerst beim endgültigen Schließen () gemeldet werden. Wenn der Rückgabewert beim Schließen der Datei nicht überprüft wird, kann dies zu einem stillen Datenverlust führen. Dies ist insbesondere bei NFS und bei Festplattenkontingenten zu beobachten.
Nick Craig-Wood
12
Also, was ist eine "große" Datei? 1 KB? 1 MB? 1 GB? Oder hängt "groß" von der Hardware der Maschine ab?
425nesp
3
@ 425nesp Es liest die gesamte Datei in den Speicher, es hängt also von der Menge des verfügbaren Speichers auf dem laufenden Computer ab.
Mostafa
49

Das ist eine gute Version:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}
Piotr
quelle
8
Dadurch wird die gesamte Datei gespeichert. Da die Datei groß sein kann, ist dies möglicherweise nicht immer das, was Sie tun möchten.
user7610
9
Auch 0x777ist falsch. In jedem Fall sollte es eher wie 0644oder 0755(oktal, nicht hex) sein.
cnst
@cnst änderte es in 0644 von 0x777
Trenton
31

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

Wenn Sie das Rad nicht neu erfinden möchten, kann das io.Copyund io.CopyNIhnen gute Dienste leisten . Wenn Sie die Quelle der io.Copy-Funktion überprüfen , handelt es sich nur um eine der Mostafa-Lösungen (eigentlich die 'grundlegende'), die in der Go-Bibliothek enthalten sind. Sie verwenden jedoch einen wesentlich größeren Puffer als er.

user7610
quelle
5
Eine Sache, die es wert ist, erwähnt zu werden - um sicherzugehen, dass der Inhalt der Datei auf die Festplatte geschrieben wurde, müssen Sie sie w.Sync()nach demio.Copy(w, r)
Shay Tsadok
Wenn Sie in eine bereits vorhandene Datei io.Copy()schreiben , werden nur die Daten geschrieben, mit denen Sie sie füttern. Wenn also eine vorhandene Datei mehr Inhalt enthält, wird sie nicht entfernt, was zu einer beschädigten Datei führen kann.
Invidian
1
@Invidian Es hängt alles davon ab, wie Sie die Zieldatei öffnen. Wenn Sie dies tun w, err := os.Create("output.txt"), geschieht das , was Sie beschreiben, nicht, da "Erstellen die benannte Datei erstellt oder abschneidet. Wenn die Datei bereits vorhanden ist, wird sie abgeschnitten." golang.org/pkg/os/#Create .
user7610
Dies sollte die richtige Antwort sein, da das Rad nicht neu erfunden wird, ohne dass die gesamte Datei vor dem Lesen auf einmal gelesen werden muss.
Eli Davis
11

Mit neueren Go-Versionen ist das Lesen / Schreiben in / aus einer Datei einfach. So lesen Sie aus einer Datei:

package main

import (
    "fmt"
    "io/ioutil"
)

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

So schreiben Sie in eine Datei:

package main

import "os"

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

    file.WriteString("test\nhello")
}

Dadurch wird der Inhalt einer Datei überschrieben (erstellen Sie eine neue Datei, wenn diese nicht vorhanden war).

Salvador Dali
quelle
10

[]byteist ein Slice (ähnlich einem Teilstring) eines gesamten oder eines Teils eines Byte-Arrays. Stellen Sie sich das Slice als eine Wertestruktur mit einem versteckten Zeigerfeld vor, mit dem das System ein Array (das Slice) ganz oder teilweise lokalisieren und darauf zugreifen kann, sowie Felder für die Länge und Kapazität des Slice, auf die Sie mit den Funktionen len()und zugreifen cap()können .

Hier ist ein funktionierendes Starter-Kit für Sie, das eine Binärdatei liest und druckt. Sie müssen den inNameLiteralwert ändern , um auf eine kleine Datei auf Ihrem System zu verweisen.

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
quelle
9
Die Go-Konvention besteht darin, zuerst nach Fehlern zu if
suchen
@Jurily: Wenn die Datei zum Zeitpunkt des Fehlers geöffnet ist, wie schließen Sie sie?
PeterSO
10
@ PeterSO: Verwenden Sie Defer
James Antill
Aber warum wird ein [256] Byte nicht akzeptiert und das eindeutig alberne und ausführliche (aber anscheinend nicht falsche) inBuf: = make ([] Byte, 256) akzeptiert?
Cardiff Space Man
7

Versuche dies:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}
Vermarkter
quelle
1
Dies funktioniert, wenn Sie die gesamte Datei auf einmal lesen möchten. Wenn die Datei wirklich groß ist oder Sie nur einen Teil davon lesen möchten, ist sie möglicherweise nicht das, wonach Sie suchen.
Evan Shaw
3
Sie sollten den Fehlercode wirklich überprüfen und ihn nicht so ignorieren !!
Hasen
7
Dies wurde jetzt in das ioutil-Paket verschoben. Also wäre es ioutil.ReadFile ()
Christopher
Ich habe es so behoben, dass 0644
Joakim
1

Wenn Sie sich nur die Dokumentation ansehen, sollten Sie einfach einen Puffer vom Typ [] byte deklarieren und an read übergeben, der dann bis zu so viele Zeichen liest und die Anzahl der tatsächlich gelesenen Zeichen (und einen Fehler) zurückgibt.

Die Dokumente sagen

Read liest bis zu len (b) Bytes aus der Datei. Es gibt die Anzahl der gelesenen Bytes und gegebenenfalls einen Fehler zurück. EOF wird durch einen Nullzähler signalisiert, wobei err auf EOF gesetzt ist.

Funktioniert das nicht

EDIT: Außerdem denke ich, dass Sie vielleicht die im bufio- Paket deklarierten Reader / Writer-Schnittstellen verwenden sollten , anstatt das os- Paket zu verwenden.

Hannes Ovrén
quelle
Sie haben meine Stimme, weil Sie tatsächlich anerkennen, was echte Menschen sehen, wenn sie die Dokumentation lesen, anstatt zu papageien, woran diejenigen, die an Go gewöhnt sind, erinnert werden (nicht daran erinnert), wenn sie die Dokumentation der Funktion lesen, mit der sie bereits vertraut sind.
Cardiff Space Man
1

Die Read-Methode verwendet einen Byte-Parameter, da dies der Puffer ist, in den gelesen wird. In manchen Kreisen ist es eine gängige Redewendung und macht Sinn, wenn man darüber nachdenkt.

Auf diese Weise können Sie bestimmen, wie viele Bytes vom Reader gelesen werden, und die Rückgabe überprüfen, um festzustellen, wie viele Bytes tatsächlich gelesen wurden, und Fehler entsprechend behandeln.

Wie andere in ihren Antworten gezeigt haben, ist Bufio wahrscheinlich das, was Sie zum Lesen aus den meisten Dateien wünschen.

Ich werde noch einen Hinweis hinzufügen, da es wirklich nützlich ist. Das Lesen einer Zeile aus einer Datei erfolgt am besten nicht mit der ReadLine-Methode, sondern mit der ReadBytes- oder ReadString-Methode.

Jeremy Wall
quelle