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.Scandevolvefalsetanto no fim do arquivo quanto em um erro, e só oErrdiferencia 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.ScanRunesentrega 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:
| Flag | Significado |
|---|---|
os.O_RDONLY, os.O_WRONLY, os.O_RDWR | abrir para leitura, escrita ou as duas (escolha uma) |
os.O_CREATE | criar o arquivo se ele não existir |
os.O_TRUNC | esvaziar o arquivo ao abrir |
os.O_APPEND | toda escrita vai para o final |
os.O_EXCL | com 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
| Tarefa | Função |
|---|---|
| criar um diretório | os.Mkdir(path, 0o755) |
| criar um caminho com os pais | os.MkdirAll(path, 0o755) |
| listar um diretório | os.ReadDir(path) |
| percorrer uma árvore | filepath.WalkDir(root, fn) |
| apagar um arquivo ou diretório vazio | os.Remove(path) |
| apagar uma árvore | os.RemoveAll(path) |
| renomear ou mover | os.Rename(old, new) |
| arquivo ou diretório temporário | os.CreateTemp("", "prefix-*"), os.MkdirTemp("", "prefix") |
| juntar partes de caminho | filepath.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 todoRead,WriteouCloseposterior nele devolveinvalid 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
Flushem umbufio.Writer. Falta o fim do arquivo. - Ignorar o erro do
Closedepois 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.
644não é0o644. O Go lê644como o número decimal, que é0o1204e 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".