Jak czytać / zapisywać z / do pliku za pomocą Go?

284

Próbowałem nauczyć się Go na własną rękę, ale z trudem próbowałem czytać i pisać w zwykłych plikach.

Mogę dojść tak daleko inFile, _ := os.Open(INFILE, 0, 0), ale w rzeczywistości uzyskanie zawartości pliku nie ma sensu, ponieważ funkcja odczytu przyjmuje []bytejako parametr.

func (file *File) Read(b []byte) (n int, err Error)
Seth Hoenig
źródło

Odpowiedzi:

476

Zróbmy listę kompatybilną z Go 1 wszystkich sposobów odczytu i zapisu plików w Go.

Ponieważ interfejs API plików ostatnio się zmienił i większość innych odpowiedzi nie działa z Go 1. Oni również tęsknią bufio ważnego IMHO.

W poniższych przykładach kopiuję plik, czytając go i zapisując do pliku docelowego.

Zacznij od podstaw

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

Tutaj użyłem os.Openi os.Createktóre są wygodnymi opakowaniami os.OpenFile. Zwykle nie musimy dzwonić OpenFilebezpośrednio.

Zwróć uwagę na leczenie EOF. Readpróbuje wypełnić bufkażde wywołanie i zwraca io.EOFjako błąd, jeśli do tego dojdzie do końca pliku. W takim przypadku bufnadal będą przechowywane dane. Kolejne wywołania Readzwracają zero jako liczbę odczytanych bajtów i taką samą io.EOFjak błąd. Każdy inny błąd doprowadzi do paniki.

Za pomocą 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)
    }
}

bufiodziała tutaj tylko jako bufor, ponieważ nie mamy wiele wspólnego z danymi. W większości innych sytuacji (szczególnie z plikami tekstowymi) bufiojest bardzo przydatny, ponieważ zapewnia nam przyjemny interfejs API do łatwego i elastycznego czytania i pisania, a także obsługuje buforowanie za sceną.

Za pomocą 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)
    }
}

Proste jak ciasto! Ale używaj go tylko wtedy, gdy masz pewność, że nie masz do czynienia z dużymi plikami.

Mostafa
źródło
55
Każdy, kto natknie się na to pytanie, został pierwotnie zadany w 2009 roku, zanim te biblioteki zostały wprowadzone, więc proszę, użyj tej odpowiedzi jako przewodnika!
Seth Hoenig
1
Według golang.org/pkg/os/#File.Write , gdy Write nie zapisał wszystkich bajtów, zwraca błąd. Dlatego dodatkowe sprawdzenie w pierwszym przykładzie ( panic("error in writing")) nie jest konieczne.
ayek
15
Zauważ, że te przykłady nie sprawdzają powrotu błędu z fo.Close (). Ze stron podręcznika systemu Linux close (2): Nie sprawdzanie wartości zwracanej przez close () jest częstym, ale jednak poważnym błędem programistycznym. Jest całkiem możliwe, że błędy poprzedniej operacji write (2) są najpierw zgłaszane przy końcowym close (). Nie sprawdzenie wartości zwracanej podczas zamykania pliku może prowadzić do cichej utraty danych. Można to szczególnie zaobserwować w przypadku NFS i przydziału dysku.
Nick Craig-Wood
12
Co to jest „duży” plik? 1KB? 1 MB? 1 GB? Czy też „duży” zależy od sprzętu maszyny?
425nesp
3
@ 425nesp Odczytuje cały plik do pamięci, więc zależy to od ilości dostępnej pamięci w uruchomionym komputerze.
Mostafa
49

To jest dobra wersja:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}
Piotr
źródło
8
To przechowuje cały plik w pamięci. Ponieważ plik może być duży, to nie zawsze musi być to, co chcesz zrobić.
user7610
9
Również 0x777jest fałszywy. W każdym przypadku powinno być więcej jak 0644lub 0755(ósemkowy, a nie hex).
cnst
@cnst zmienił go na 0644 z 0x777
Trenton
31

Za pomocą 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)
}

Jeśli nie masz ochoty wymyślać koła na nowo, io.Copyi io.CopyNmoże ci dobrze służyć. Jeśli sprawdzisz źródło funkcji io.Copy, jest to jedno z rozwiązań Mostafa (właściwie „podstawowe”) spakowane w bibliotece Go. Jednak używają znacznie większego bufora niż on.

użytkownik7610
źródło
5
jedna rzecz, o której warto wspomnieć - aby mieć pewność, że zawartość pliku została zapisana na dysku, musisz użyć w.Sync()poio.Copy(w, r)
Shay Tsadok
Ponadto, jeśli napiszesz do już istniejącego pliku, io.Copy()zapisze tylko dane, którymi go karmisz, więc jeśli istniejący plik zawiera więcej treści, nie zostanie usunięty, co może spowodować uszkodzenie pliku.
Invidian
1
@Invidian Wszystko zależy od tego, jak otworzysz plik docelowy. Jeśli to zrobisz w, err := os.Create("output.txt"), to, co opisujesz, nie nastąpi, ponieważ „Utwórz tworzy lub obcina nazwany plik. Jeśli plik już istnieje, jest obcięty”. golang.org/pkg/os/#Create .
user7610
To powinna być prawidłowa odpowiedź, ponieważ nie wymyśla koła na nowo, ale nie musi jednocześnie czytać całego pliku przed jego odczytaniem.
Eli Davis
11

Dzięki nowszym wersjom Go odczyt / zapis do / z pliku jest łatwy. Aby odczytać z pliku:

package main

import (
    "fmt"
    "io/ioutil"
)

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

Aby zapisać do pliku:

package main

import "os"

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

    file.WriteString("test\nhello")
}

Spowoduje to zastąpienie zawartości pliku (utwórz nowy plik, jeśli go nie było).

Salvador Dali
źródło
10

[]bytejest wycinkiem (podobnym do podłańcucha) całości lub części tablicy bajtów. Pomyśl o wycinku jako strukturze wartości z ukrytym polem wskaźnika, aby system mógł zlokalizować i uzyskać dostęp do całości lub części tablicy (wycinka), a także pola dotyczące długości i pojemności wycinka, do których można uzyskać dostęp za pomocą funkcji len()i cap().

Oto działający zestaw startowy, który odczytuje i drukuje plik binarny; musisz zmienić inNamewartość literału, aby odnosić się do małego pliku w systemie.

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
źródło
9
Konwencja Go polega najpierw na sprawdzeniu błędu i pozostawieniu normalnego kodu poza ifblokiem
hasen
@Jurily: Jeśli plik jest otwarty, gdy wystąpi błąd, jak go zamknąć?
peterSO
10
@peterSO: użyj odroczenia
James Antill,
Ale dlaczego bajt [256] nie jest akceptowany i akceptowany jest wyraźnie głupiutki i pełny (ale najwyraźniej niepoprawny) inBuf: = make ([] bajt, 256)?
mężczyzna z kosmosu w Cardiff
7

Spróbuj tego:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}
marketingowiec
źródło
1
Działa to, jeśli chcesz odczytać cały plik na raz. Jeśli plik jest naprawdę duży lub chcesz tylko przeczytać jego część, może nie być to, czego szukasz.
Evan Shaw,
3
Naprawdę powinieneś sprawdzić kod błędu i nie ignorować go w ten sposób !!
hasen
7
Zostało to teraz przeniesione do pakietu ioutil. Byłby to więc ioutil.ReadFile ()
Christopher
Naprawiłem, więc jest napisane 0644
Joakim
1

Patrząc na dokumentację, wydaje się, że powinieneś po prostu zadeklarować bufor typu [] bajt i przekazać go do odczytu, który następnie odczyta aż tyle znaków i zwróci liczbę faktycznie odczytanych znaków (i błąd).

Doktorzy mówią

Odczyt czyta z pliku do len (b) bajtów. Zwraca liczbę odczytanych bajtów i ewentualny błąd. EOF jest sygnalizowany zerowym zliczeniem z err ustawionym na EOF.

Czy to nie działa?

EDYCJA: Myślę też, że powinieneś raczej użyć interfejsów Reader / Writer zadeklarowanych w pakiecie bufio zamiast używać pakietu os .

Hannes Ovrén
źródło
Masz mój głos, ponieważ faktycznie potwierdzasz, co widzą prawdziwi ludzie, kiedy czytają dokumentację, zamiast paragrafować o tym, co ludzie przyzwyczajeni do Go są PRZYPOMNIENI (nie czytają PRZYPOMNIONYCH), kiedy czytają dokumentację funkcji, którą już znają.
mężczyzna z kosmosu w Cardiff
1

Metoda Read pobiera parametr bajtowy, ponieważ jest to bufor, w którym będzie czytać. Jest to powszechny idiom w niektórych kręgach i ma sens, gdy się nad nim zastanowić.

W ten sposób możesz określić, ile bajtów zostanie odczytanych przez czytnik i sprawdzić zwrot, aby zobaczyć, ile bajtów faktycznie odczytano, i odpowiednio obsłużyć wszelkie błędy.

Jak wskazali inni w swoich odpowiedziach, bufio jest prawdopodobnie tym, czego chcesz czytać z większości plików.

Dodam jeszcze jedną wskazówkę, ponieważ jest naprawdę przydatna. Odczyt wiersza z pliku najlepiej osiągnąć nie metodą ReadLine, ale metodą ReadBytes lub ReadString.

Jeremy Wall
źródło