Menu

Ler e escrever arquivos em Golang: os, bufio e diretórios

Como ler e escrever arquivos em Go: os.ReadFile e os.WriteFile, ler linha por linha com bufio.Scanner, acrescentar com os.OpenFile, verificar se um arquivo existe e trabalhar com diretórios.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Ler e escrever um arquivo inteiro

os.WriteFile e os.ReadFile cobrem a maioria das necessidades. Eles abrem, escrevem ou leem, e fecham em uma só chamada.

Os exemplos desta página trabalham em um diretório temporário criado com os.MkdirTemp e o removem com defer os.RemoveAll(dir), então não deixam nada para trás. No seu próprio código, um caminho relativo como "config.json" é resolvido a partir do diretório de trabalho do processo, que não é necessariamente o diretório do arquivo-fonte nem o do binário.

os.WriteFile cria o arquivo se necessário e o trunca se ele existir. O terceiro argumento é a permissão Unix de um arquivo recém-criado: 0o644 significa que o dono pode ler e escrever, e todos os outros podem ler. Ele é ignorado para um arquivo que já existe, e a umask do processo pode remover bits.

os.ReadFile lê tudo para a memória. Isso está certo para arquivos de configuração e entradas pequenas, e errado para um log de vários gigabytes.

Ler linha por linha com bufio.Scanner

Para arquivos grandes, ou quando você quer as linhas de qualquer forma, use bufio.Scanner. Ele lê em blocos e entrega uma linha de cada vez, sem a quebra de linha.

Três detalhes:

  • Verifique sc.Err() depois do laço. Scan devolve false tanto no fim do arquivo quanto em um erro, e só o Err diferencia os dois.
  • O limite de 64 KB por linha. Por padrão, uma única linha com mais de 64 KB para o scanner com bufio.Scanner: token too long. Para arquivos com linhas longas (JSON minificado, alguns logs), aumente o limite antes do laço: sc.Buffer(make([]byte, 1024*1024), 10*1024*1024).
  • Outras unidades. sc.Split(bufio.ScanWords) entrega palavras; bufio.ScanRunes entrega caracteres.

Para ler um fluxo em blocos de tamanho fixo em vez de linhas, use f.Read(buf) em um laço ou io.Copy para outro writer.

Escrevendo: os.Create, os.OpenFile e append

os.Create(name) abre um arquivo para escrita, criando-o ou truncando-o. os.OpenFile dá controle total por meio de flags:

FlagSignificado
os.O_RDONLY, os.O_WRONLY, os.O_RDWRabrir para leitura, escrita ou as duas (escolha uma)
os.O_CREATEcriar o arquivo se ele não existir
os.O_TRUNCesvaziar o arquivo ao abrir
os.O_APPENDtoda escrita vai para o final
os.O_EXCLcom O_CREATE: falhar se o arquivo já existir

os.Open(name) é OpenFile(name, O_RDONLY, 0). os.Create(name) é OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0o666).

Ao escrever, o erro do Close importa. Alguns sistemas de arquivos só reportam falhas de escrita na hora de fechar, então um defer f.Close() sozinho pode esconder dados perdidos. Para arquivos em que você escreve, verifique o Close explicitamente, como faz appendLine. Para arquivos que você só lê, defer f.Close() basta.

fmt.Fprintln e as outras funções de impressão do fmt aceitam qualquer io.Writer, inclusive um arquivo. Um bufio.Writer agrupa escritas pequenas na memória. Esquecer o w.Flush() é um bug clássico: o programa termina normalmente e os últimos kilobytes nunca chegam ao arquivo.

O arquivo existe?

Go não tem os.Exists. Chame os.Stat e inspecione o erro:

errors.Is(err, fs.ErrNotExist) é o idioma atual. Ele substitui o antigo os.IsNotExist(err), que não enxerga através de erros empacotados.

Verificar antes de abrir muitas vezes é desnecessário e sujeito a race: o arquivo pode aparecer ou sumir entre a verificação e a abertura. Normalmente você só abre e trata o fs.ErrNotExist da abertura. Para criar um arquivo só se ele ainda não existir, use O_CREATE|O_EXCL, que faz da verificação e da criação um único passo atômico.

Diretórios

TarefaFunção
criar um diretórioos.Mkdir(path, 0o755)
criar um caminho com os paisos.MkdirAll(path, 0o755)
listar um diretórioos.ReadDir(path)
percorrer uma árvorefilepath.WalkDir(root, fn)
apagar um arquivo ou diretório vazioos.Remove(path)
apagar uma árvoreos.RemoveAll(path)
renomear ou moveros.Rename(old, new)
arquivo ou diretório temporárioos.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix")
juntar partes de caminhofilepath.Join(a, b, c)

Use path/filepath para caminhos do sistema de arquivos: ele usa o separador certo para o sistema operacional (\ no Windows). O pacote path é para caminhos separados por barra, como URLs.

O Go 1.24 também trouxe o os.Root (os.OpenRoot(dir)), que só abre arquivos dentro de um diretório e recusa caminhos que escapam dele com .. ou symlinks. Use-o quando os nomes de arquivo vierem de usuários.

Erros comuns

  • Não verificar erros. Todas essas chamadas podem falhar. Um arquivo que falhou ao abrir é nil, e todo Read, Write ou Close posterior nele devolve invalid argument, o que esconde a causa real (o arquivo não existia, ou a permissão foi negada).
  • Esquecer sc.Err() depois de um laço de scan. Um erro de leitura parece fim de arquivo.
  • Esquecer o Flush em um bufio.Writer. Falta o fim do arquivo.
  • Ignorar o erro do Close depois de escrever. Erros de escrita podem aparecer só ali.
  • defer f.Close() dentro de um laço sobre muitos arquivos. Os arquivos ficam abertos até a função retornar e você pode ficar sem descritores de arquivo. Mova o corpo para uma função, para que cada arquivo feche a cada iteração.
  • Permissões escritas em decimal. 644 não é 0o644. O Go lê 644 como o número decimal, que é 0o1204 e define bits estranhos.

Perguntas frequentes

Como ler um arquivo inteiro para uma string em Go?

data, err := os.ReadFile("notes.txt") devolve o conteúdo como []byte; converta com string(data). Ele abre, lê e fecha o arquivo por você. Use para arquivos que cabem com folga na memória; para arquivos grandes, leia linha por linha com bufio.Scanner.

Como ler um arquivo linha por linha em Go?

Abra o arquivo com os.Open, faça defer f.Close(), envolva-o em bufio.NewScanner(f), percorra com for sc.Scan() { line := sc.Text() } e verifique sc.Err() depois do laço. Linhas com mais de 64 KB fazem o scanner falhar com token too long, a menos que você aumente o buffer dele com sc.Buffer.

Como acrescentar conteúdo a um arquivo em Go?

Abra-o com os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644), escreva e verifique o erro do Close. O_CREATE cria o arquivo se ele não existir, e O_APPEND faz toda escrita ir para o final.

Como verificar se um arquivo existe em Go?

Chame os.Stat(path) e teste o erro com errors.Is(err, fs.ErrNotExist). Um erro nil significa que ele existe. Qualquer outro erro (permissão negada, por exemplo) significa que você não consegue saber, então trate-o separadamente em vez de considerá-lo como "não existe".

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR