Como ler / gravar de / para arquivo usando o Go?

284

Eu tenho tentado aprender o Go sozinho, mas fiquei perplexo ao tentar ler e gravar em arquivos comuns.

Eu posso ir tão longe quanto inFile, _ := os.Open(INFILE, 0, 0), mas, na verdade, obter o conteúdo do arquivo não faz sentido, porque a função de leitura assume []bytecomo parâmetro.

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

Respostas:

476

Vamos fazer uma lista compatível com Go 1 de todas as maneiras de ler e gravar arquivos no Go.

Como a API do arquivo foi alterada recentemente e a maioria das outras respostas não funciona com o Go 1. Eles também ignoram o bufioque é importante no IMHO.

Nos exemplos a seguir, copio um arquivo lendo e escrevendo no arquivo de destino.

Comece com o básico

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

Aqui eu usei os.Opene os.Createquais são embalagens convenientes ao redor os.OpenFile. Normalmente, não precisamos ligar OpenFilediretamente.

Aviso tratando EOF. Readtenta preencher bufcada chamada e retorna io.EOFcomo erro se chegar ao final do arquivo ao fazê-lo. Nesse caso buf, ainda manterá os dados. Chamadas consequentes para Readretorna zero como o número de bytes lidos e o mesmo io.EOFque erro. Qualquer outro erro levará a um pânico.

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

bufioestá apenas atuando como um buffer aqui, porque não temos muito a ver com dados. Na maioria das outras situações (especialmente com arquivos de texto)bufio é muito útil, oferecendo uma boa API para leitura e gravação de maneira fácil e flexível, enquanto lida com o buffer nos bastidores.

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

Fácil como torta! Mas use-o apenas se tiver certeza de que não está lidando com arquivos grandes.

Mostafa
fonte
55
Para quem se deparar com essa pergunta, ela foi originalmente perguntada em 2009 antes da introdução dessas bibliotecas; portanto, use esta resposta como seu guia!
Seth Hoenig
1
De acordo com golang.org/pkg/os/#File.Write , quando Write não gravou todos os bytes, ele retorna um erro. Portanto, a verificação extra no primeiro exemplo ( panic("error in writing")) não é necessária.
ayke
15
Observe que esses exemplos não estão verificando o retorno de erro de fo.Close (). Nas páginas de manual do Linux, close (2): não verificar o valor de retorno de close () é um erro de programação comum, mas grave. É bem possível que erros em uma operação de gravação anterior (2) sejam relatados primeiro no fechamento final (). Não verificar o valor de retorno ao fechar o arquivo pode levar à perda silenciosa de dados. Isso pode ser observado especialmente com o NFS e com a cota de disco.
Nick Craig-Wood
12
Então, o que é um arquivo "grande"? 1 KB? 1MB? 1GB? Ou "grande" depende do hardware da máquina?
425nesp
3
@ 425nesp Lê todo o ficheiro na memória, por isso depende da quantidade de memória disponível na máquina em execução.
Mostafa
49

Esta é uma boa versão:

package main

import (
  "io/ioutil"; 
  )


func main() {
  contents,_ := ioutil.ReadFile("plikTekstowy.txt")
  println(string(contents))
  ioutil.WriteFile("filename", contents, 0644)
}
Piotr
fonte
8
Isso armazena o arquivo inteiro na memória. Como o arquivo pode ser grande, isso pode não ser sempre o que você deseja fazer.
user7610
9
Além disso, 0x777é falso. Em qualquer caso, deve ser mais parecido com 0644ou 0755(octal, não hexadecimal).
usar o seguinte comando
@cnst alterou para 0644 de 0x777
Trenton
31

Usando 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 você não sente como reinventar a roda, o io.Copye io.CopyNpodem atendê-lo bem. Se você verificar a fonte da função io.Copy, ela não passa de uma das soluções do Mostafa (a 'básica', na verdade) empacotada na biblioteca Go. Eles estão usando um buffer significativamente maior do que ele, no entanto.

user7610
fonte
5
uma coisa que vale a pena mencionar - para ter certeza de que o conteúdo do arquivo foi gravado no disco, você precisa usá-lo w.Sync()após oio.Copy(w, r)
Shay Tsadok
Além disso, se você gravar em um arquivo já existente, io.Copy()gravará apenas os dados com os quais você o alimenta; portanto, se o arquivo existente tiver mais conteúdo, ele não será removido, o que pode resultar em um arquivo corrompido.
Invidian
1
@Invidian Tudo depende de como você abre o arquivo de destino. Se você o fizer w, err := os.Create("output.txt"), o que você descreve não acontecerá, porque "Criar cria ou trunca o arquivo nomeado. Se o arquivo já existir, ele será truncado". golang.org/pkg/os/#Create .
user7610 1/03
Essa deve ser a resposta correta, pois não reinventa a roda, sem precisar ler o arquivo inteiro de uma só vez antes de lê-lo.
Eli Davis
11

Com as versões mais recentes do Go, é fácil ler / gravar em / de arquivo. Para ler de um arquivo:

package main

import (
    "fmt"
    "io/ioutil"
)

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

Para gravar em um arquivo:

package main

import "os"

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

    file.WriteString("test\nhello")
}

Isso substituirá o conteúdo de um arquivo (crie um novo arquivo se ele não estiver lá).

Salvador Dalí
fonte
10

[]byteé uma fatia (semelhante a uma substring) de todo ou parte de uma matriz de bytes. Pense na fatia como uma estrutura de valor com um campo de ponteiro oculto para o sistema localizar e acessar toda ou parte de uma matriz (a fatia), além de campos para o comprimento e capacidade da fatia, que você pode acessar usando as funções len()e cap().

Aqui está um kit inicial para você, que lê e imprime um arquivo binário; você precisará alterar o inNamevalor literal para se referir a um pequeno arquivo em seu 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();
}
peterSO
fonte
9
A convenção Go é para verificar se há erros em primeiro lugar, e deixar que o normal fora reside código do ifbloco
Hasen
@ Jurly: Se o arquivo está aberto quando o erro ocorre, como você o fecha?
peterSO
10
@peterSO: use defer
James Antill
Mas por que um [256] byte não é aceito e o claramente tolo e detalhado (mas aparentemente não está errado) emBuf: = make ([] byte, 256) aceito?
cardiff space man
7

Tente o seguinte:

package main

import (
  "io"; 
  )


func main() {
  contents,_ := io.ReadFile("filename");
  println(string(contents));
  io.WriteFile("filename", contents, 0644);
}
comerciante
fonte
1
Isso funcionará se você quiser ler o arquivo inteiro de uma vez. Se o arquivo é realmente grande ou você deseja apenas ler parte dele, talvez não seja o que você está procurando.
Evan Shaw
3
Você realmente deve verificar o código de erro e não ignorá-lo assim !!
hasen
7
Isso foi movido para o pacote ioutil agora. Então, seria ioutil.ReadFile ()
Christopher
Eu consertei assim diz 0644
Joakim
1

Olhando apenas para a documentação, parece que você deve declarar um buffer do tipo [] byte e passá-lo para ler, que lerá até muitos caracteres e retornará o número de caracteres realmente lidos (e um erro).

Os documentos dizem

Leitura lê até len (b) bytes do arquivo. Retorna o número de bytes lidos e um Erro, se houver. O EOF é sinalizado por uma contagem zero com o erro definido como EOF.

Isso não funciona?

EDIT: Além disso, acho que você talvez deva usar as interfaces Reader / Writer declaradas no pacote bufio em vez de usar o pacote os .

Hannes Ovrén
fonte
Você tem meu voto porque realmente reconhece o que as pessoas reais veem quando lêem a documentação, em vez de papagaiar o que os que estão acostumados a Go são LEMBRADOS (não leram LEMBRADOS DE) quando lêem a documentação da função com a qual já estão familiarizados.
cardiff space man
1

O método Read usa um parâmetro de byte porque esse é o buffer no qual ele lerá. É um idioma comum em alguns círculos e faz algum sentido quando você pensa sobre isso.

Dessa forma, você pode determinar quantos bytes serão lidos pelo leitor e inspecionar o retorno para ver quantos bytes foram realmente lidos e manipular os erros adequadamente.

Como outros já apontaram em suas respostas, bufio é provavelmente o que você deseja ler na maioria dos arquivos.

Vou adicionar outra dica, já que é realmente útil. A leitura de uma linha de um arquivo é melhor realizada não pelo método ReadLine, mas pelo método ReadBytes ou ReadString.

Jeremy Wall
fonte