Trabalhando com arquivos (os)
Esta aula aborda a manipulação de arquivos em Go usando o pacote os, incluindo abertura, leitura, escrita e gerenciamento de permissões, com foco no tratamento de erros. Você aprenderá a usar funções como os.Open, os.Create, os.WriteFile e a definir permissões com os.FileMode. Exemplos práticos e exercícios consolidam o aprendizado.
Manipular arquivos é uma tarefa fundamental em muitas aplicações, seja para persistir dados, ler configurações ou gerar relatórios. Em Go, o pacote os fornece funções de baixo nível para operações de arquivo, enquanto o pacote io e bufio oferecem abstrações adicionais. Nesta aula, focaremos nas funções essenciais do pacote os para abrir, ler, escrever e definir permissões em arquivos, sempre com atenção ao tratamento de erros, que é uma prática obrigatória em Go.
O pacote os segue o padrão de retornar um valor de erro como último valor de retorno, permitindo que o programador trate explicitamente qualquer falha. Veremos desde a abertura de um arquivo existente até a criação de novos arquivos com permissões específicas, além de operações de leitura e escrita em blocos de bytes.
Abrindo e lendo
Para abrir um arquivo existente, usamos os.Open, que retorna um ponteiro para os.File e um erro. O arquivo é aberto no modo somente leitura. Para leitura, podemos usar métodos como Read (lê um slice de bytes) ou ReadAll do pacote io para ler todo o conteúdo. É importante fechar o arquivo com defer file.Close() para liberar recursos.
Exemplo: abrir um arquivo, ler seu conteúdo e exibir no console.
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("exemplo.txt")
if err != nil {
fmt.Println("Erro ao abrir arquivo:", err)
return
}
defer file.Close()
// Lê o arquivo em um slice de bytes
data := make([]byte, 100)
count, err := file.Read(data)
if err != nil {
fmt.Println("Erro ao ler arquivo:", err)
return
}
fmt.Printf("Lidos %d bytes: %s\n", count, data[:count])
}
Para ler todo o arquivo de uma vez, podemos usar io.ReadAll ou os.ReadFile (disponível a partir do Go 1.16). os.ReadFile é mais simples, pois abre, lê e fecha o arquivo automaticamente.
package main
import (
"fmt"
"os"
)
func main() {
data, err := os.ReadFile("exemplo.txt")
if err != nil {
fmt.Println("Erro ao ler arquivo:", err)
return
}
fmt.Println(string(data))
}
Escrevendo
Para escrever em um arquivo, podemos usar os.Create (cria ou trunca um arquivo existente) ou os.OpenFile com flags para controle mais fino. O método Write escreve um slice de bytes, e WriteString escreve uma string. Assim como na leitura, é fundamental fechar o arquivo após a escrita.
Exemplo: criar um arquivo e escrever uma string.
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Create("saida.txt")
if err != nil {
fmt.Println("Erro ao criar arquivo:", err)
return
}
defer file.Close()
bytesWritten, err := file.WriteString("Olá, mundo!\n")
if err != nil {
fmt.Println("Erro ao escrever:", err)
return
}
fmt.Printf("Escritos %d bytes\n", bytesWritten)
}
Também podemos usar os.WriteFile para escrever diretamente um slice de bytes em um arquivo, similar ao ReadFile.
package main
import (
"fmt"
"os"
)
func main() {
data := []byte("Conteúdo escrito com WriteFile\n")
err := os.WriteFile("saida2.txt", data, 0644)
if err != nil {
fmt.Println("Erro:", err)
return
}
fmt.Println("Arquivo escrito com sucesso")
}
Note que os.WriteFile aceita um terceiro argumento para as permissões do arquivo (veremos a seguir).
Permissões
As permissões em sistemas Unix são representadas por um valor octal de 9 bits (rwx para dono, grupo e outros). Em Go, o tipo os.FileMode encapsula essas permissões. Constantes como 0644 (dono pode ler/escrever, grupo e outros só leitura) são comuns. Ao criar um arquivo com os.Create, as permissões padrão são 0666 (antes da máscara umask). Para definir permissões específicas, use os.OpenFile com a flag os.O_CREATE e o modo desejado.
Exemplo: criar um arquivo com permissões restritas (apenas dono pode ler/escrever).
package main
import (
"fmt"
"os"
)
func main() {
// Abre ou cria o arquivo com permissões 0600
file, err := os.OpenFile("privado.txt", os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
fmt.Println("Erro:", err)
return
}
defer file.Close()
file.WriteString("Conteúdo privado")
}
Para alterar permissões de um arquivo existente, use os.Chmod.
package main
import (
"fmt"
"os"
)
func main() {
err := os.Chmod("exemplo.txt", 0644)
if err != nil {
fmt.Println("Erro ao alterar permissões:", err)
return
}
fmt.Println("Permissões alteradas")
}
Tratamento de erros
Em Go, o tratamento de erros é explícito. Quase todas as funções do pacote os retornam um erro como último valor. Você deve verificar esse erro imediatamente. Ignorar erros pode levar a comportamentos inesperados ou panics. Use a declaração if err != nil para tratar o erro adequadamente, seja exibindo uma mensagem, retornando ou tomando uma ação corretiva.
Exemplo: tratamento de erro ao abrir um arquivo que não existe.
package main
import (
"fmt"
"os"
)
func main() {
_, err := os.Open("inexistente.txt")
if err != nil {
// Verifica se o erro é do tipo *os.PathError
if os.IsNotExist(err) {
fmt.Println("Arquivo não existe")
} else {
fmt.Println("Erro desconhecido:", err)
}
return
}
fmt.Println("Arquivo aberto com sucesso")
}
Funções como os.IsNotExist e os.IsPermission ajudam a identificar a natureza do erro. Sempre feche o arquivo com defer para garantir a liberação de recursos, mesmo que ocorra um erro após a abertura.
Boas práticas e observações finais
Sempre use defer file.Close() logo após abrir o arquivo, para garantir que ele será fechado ao final da função. Prefira os.ReadFile e os.WriteFile para operações simples, pois elas gerenciam a abertura e fechamento automaticamente. Para arquivos grandes, use leitura/escrita em partes com bufio para melhor desempenho. Lembre-se de que as permissões são aplicadas apenas na criação; para arquivos existentes, use os.Chmod. Trate todos os erros de forma consistente, especialmente ao trabalhar com arquivos, onde falhas podem ocorrer por diversos motivos (disco cheio, permissões, etc.).
Referências
- Documentação oficial do pacote os
- Documentação oficial do pacote io
- Usando bytes.Buffer para manipulação de dados
- Tutorial da DigitalOcean sobre leitura e escrita de arquivos em Go
- Go by Example: Reading Files
- Go by Example: Writing Files
- Guia sobre permissões de arquivos em Go
Exercícios
-
Escreva um programa que leia o conteúdo de um arquivo chamado "dados.txt" e imprima na tela o número de linhas do arquivo. Considere que cada linha termina com '\n'.
✓ Resposta:package main import ( "fmt" "os" "strings" ) func main() { data, err := os.ReadFile("dados.txt") if err != nil { fmt.Println("Erro:", err) return } lines := strings.Split(string(data), "\n") fmt.Println("Número de linhas:", len(lines)-1) // última linha vazia } -
Crie um programa que escreva os números de 1 a 10, um por linha, em um arquivo chamado "numeros.txt".
✓ Resposta:package main import ( "fmt" "os" ) func main() { file, err := os.Create("numeros.txt") if err != nil { fmt.Println("Erro:", err) return } defer file.Close() for i := 1; i <= 10; i++ { _, err := file.WriteString(fmt.Sprintf("%d\n", i)) if err != nil { fmt.Println("Erro ao escrever:", err) return } } fmt.Println("Arquivo criado com sucesso") } -
Implemente uma função que copie um arquivo de origem para um destino. Use os pacotes
oseio.✓ Resposta:package main import ( "io" "os" ) func copyFile(src, dst string) error { sourceFile, err := os.Open(src) if err != nil { return err } defer sourceFile.Close() destFile, err := os.Create(dst) if err != nil { return err } defer destFile.Close() _, err = io.Copy(destFile, sourceFile) if err != nil { return err } return destFile.Sync() // garante que os dados foram gravados } func main() { err := copyFile("origem.txt", "destino.txt") if err != nil { panic(err) } } -
Escreva um programa que leia um arquivo "config.json" e, se ele não existir, crie um com conteúdo padrão {"nome": "default"}. Use permissões 0644.
✓ Resposta:package main import ( "fmt" "os" ) func main() { _, err := os.Stat("config.json") if os.IsNotExist(err) { data := []byte(`{"nome": "default"}`) err := os.WriteFile("config.json", data, 0644) if err != nil { fmt.Println("Erro ao criar config.json:", err) return } fmt.Println("Arquivo config.json criado") } else if err != nil { fmt.Println("Erro ao verificar arquivo:", err) } else { data, err := os.ReadFile("config.json") if err != nil { fmt.Println("Erro ao ler config.json:", err) return } fmt.Println("Conteúdo:", string(data)) } } -
Modifique o programa do exercício 1 para que, em vez de ler o arquivo inteiro, leia linha por linha usando um buffer (por exemplo, com
bufio.Scanner).✓ Resposta:package main import ( "bufio" "fmt" "os" ) func main() { file, err := os.Open("dados.txt") if err != nil { fmt.Println("Erro:", err) return } defer file.Close() scanner := bufio.NewScanner(file) lineCount := 0 for scanner.Scan() { lineCount++ } if err := scanner.Err(); err != nil { fmt.Println("Erro ao ler:", err) return } fmt.Println("Número de linhas:", lineCount) }