Golang: Manipulação de arquivos (Parte 17)
Golang: Manipulação de arquivos (Parte 17)
A manipulação de arquivos é uma tarefa fundamental em muitas aplicações. Em Go, o pacote os fornece a maioria das funções necessárias para interagir com o sistema de arquivos. Este tutorial abordará as operações mais comuns, com exemplos práticos e explicações detalhadas.
1. O Pacote os
O pacote os é o principal ponto de entrada para operações de sistema de arquivos em Go. Ele oferece funções para criar, abrir, ler, escrever, renomear, remover arquivos e diretórios, além de obter informações sobre eles.
2. Criando Arquivos
Para criar um novo arquivo, você pode usar os.Create(). Se o arquivo já existir, os.Create() o truncará (limpará seu conteúdo).
package main
import (
"fmt"
"os"
)
func main() {
// Criar um novo arquivo
file, err := os.Create("exemplo.txt")
if err != nil {
fmt.Println("Erro ao criar o arquivo:", err)
return
}
defer file.Close() // Garante que o arquivo será fechado no final
fmt.Println("Arquivo 'exemplo.txt' criado com sucesso!")
}
Explicação:
os.Create("exemplo.txt"): Tenta criar um arquivo chamadoexemplo.txt. Retorna um ponteiro paraos.Filee um erro.defer file.Close(): É crucial fechar o arquivo após o uso para liberar os recursos do sistema.defergarante quefile.Close()será chamado antes que a funçãomainretorne, mesmo que ocorra um erro.
3. Escrevendo em Arquivos
Existem várias maneiras de escrever em um arquivo em Go.
3.1. Usando file.WriteString()
Ideal para escrever strings.
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Create("dados.txt")
if err != nil {
fmt.Println("Erro ao criar o arquivo:", err)
return
}
defer file.Close()
conteudo := "Olá, Go!\nEste é um exemplo de escrita em arquivo."
bytesEscritos, err := file.WriteString(conteudo)
if err != nil {
fmt.Println("Erro ao escrever no arquivo:", err)
return
}
fmt.Printf("Escritos %d bytes no arquivo 'dados.txt'\n", bytesEscritos)
}
3.2. Usando file.Write()
Para escrever slices de bytes ([]byte).
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Create("bytes.txt")
if err != nil {
fmt.Println("Erro ao criar o arquivo:", err)
return
}
defer file.Close()
dados := []byte("Escrevendo bytes diretamente.\n")
bytesEscritos, err := file.Write(dados)
if err != nil {
fmt.Println("Erro ao escrever no arquivo:", err)
return
}
fmt.Printf("Escritos %d bytes no arquivo 'bytes.txt'\n", bytesEscritos)
}
3.3. Usando io.WriteString() e bufio.Writer para Eficiência
Para grandes volumes de dados, usar um bufio.Writer pode melhorar a performance, pois ele armazena os dados em um buffer antes de escrevê-los no disco.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Create("buffered_write.txt")
if err != nil {
fmt.Println("Erro ao criar o arquivo:", err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
_, err = writer.WriteString("Linha 1 com buffer.\n")
if err != nil {
fmt.Println("Erro ao escrever no buffer:", err)
return
}
_, err = writer.WriteString("Linha 2 com buffer.\n")
if err != nil {
fmt.Println("Erro ao escrever no buffer:", err)
return
}
// É crucial chamar Flush() para garantir que todos os dados no buffer sejam gravados no arquivo.
err = writer.Flush()
if err != nil {
fmt.Println("Erro ao descarregar o buffer:", err)
return
}
fmt.Println("Dados escritos em 'buffered_write.txt' usando buffer.")
}
4. Lendo Arquivos
Go oferece várias maneiras de ler o conteúdo de um arquivo.
4.1. Lendo o Arquivo Inteiro com os.ReadFile() (Go 1.16+)
A maneira mais simples para arquivos pequenos.
package main
import (
"fmt"
"os"
)
func main() {
// Criar um arquivo para leitura
err := os.WriteFile("leitura.txt", []byte("Conteúdo do arquivo para leitura."), 0644)
if err != nil {
fmt.Println("Erro ao preparar o arquivo:", err)
return
}
// Ler o arquivo inteiro
conteudo, err := os.ReadFile("leitura.txt")
if err != nil {
fmt.Println("Erro ao ler o arquivo:", err)
return
}
fmt.Println("Conteúdo do arquivo 'leitura.txt':")
fmt.Println(string(conteudo))
}
Explicação:
os.WriteFile(): Uma função de conveniência para escrever dados em um arquivo. O0644são as permissões do arquivo (leitura/escrita para o proprietário, leitura para grupo e outros).os.ReadFile("leitura.txt"): Lê todo o conteúdo do arquivo e o retorna como um slice de bytes.
4.2. Lendo Linha por Linha com bufio.Scanner
Ideal para arquivos grandes, onde ler o arquivo inteiro na memória não é viável.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// Criar um arquivo com múltiplas linhas para leitura
file, err := os.Create("linhas.txt")
if err != nil {
fmt.Println("Erro ao criar o arquivo:", err)
return
}
_, err = file.WriteString("Primeira linha.\nSegunda linha.\nTerceira linha.")
if err != nil {
fmt.Println("Erro ao escrever no arquivo:", err)
file.Close()
return
}
file.Close() // Fechar o arquivo após a escrita
// Abrir o arquivo para leitura
file, err = os.Open("linhas.txt")
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
linhaNum := 1
for scanner.Scan() {
fmt.Printf("Linha %d: %s\n", linhaNum, scanner.Text())
linhaNum++
}
if err := scanner.Err(); err != nil {
fmt.Println("Erro ao ler o arquivo com scanner:", err)
}
}
Explicação:
os.Open("linhas.txt"): Abre um arquivo para leitura.bufio.NewScanner(file): Cria um novo scanner que lê do arquivo. Por padrão, ele divide a entrada por linhas.scanner.Scan(): Avança o scanner para a próxima linha. Retornatruese houver mais linhas,falsecaso contrário (fim do arquivo ou erro).scanner.Text(): Retorna a linha atual como uma string.scanner.Err(): Verifica se ocorreu algum erro durante a leitura.
4.3. Lendo em Chunks com file.Read()
Para controle mais granular sobre a leitura, lendo blocos de bytes.
package main
import (
"fmt"
"io"
"os"
)
func main() {
// Criar um arquivo para leitura em chunks
err := os.WriteFile("chunks.txt", []byte("Este é um arquivo para leitura em pedaços."), 0644)
if err != nil {
fmt.Println("Erro ao preparar o arquivo:", err)
return
}
file, err := os.Open("chunks.txt")
if err != nil {
fmt.Println("Erro ao abrir o arquivo:", err)
return
}
defer file.Close()
buffer := make([]byte, 5) // Buffer de 5 bytes
fmt.Println("Lendo 'chunks.txt' em pedaços:")
for {
n, err := file.Read(buffer)
if err != nil {
if err == io.EOF {
break // Fim do arquivo
}
fmt.Println("Erro ao ler o arquivo:", err)
return
}
fmt.Printf("Lidos %d bytes: %s\n", n, string(buffer[:n]))
}
}
Explicação:
buffer := make([]byte, 5): Cria um slice de bytes para atuar como buffer.file.Read(buffer): Tenta ler atélen(buffer)bytes no buffer. Retorna o número de bytes lidos (n) e um erro.io.EOF: É o erro retornado quando o fim do arquivo é alcançado.
5. Abrindo Arquivos com os.OpenFile()
os.OpenFile() oferece mais controle sobre como um arquivo é aberto, permitindo especificar o modo de abertura (somente leitura, escrita, etc.) e as permissões.
package main
import (
"fmt"
"os"
)
func main() {
// Criar um arquivo para demonstração
os.WriteFile("config.txt", []byte("config=valor"), 0644)
// Abrir para leitura (somente)
fileRead, err := os.OpenFile("config.txt", os.O_RDONLY, 0644)
if err != nil {
fmt.Println("Erro ao abrir para leitura:", err)
return
}
defer fileRead.Close()
fmt.Println("Arquivo 'config.txt' aberto para leitura.")
// Abrir para escrita (truncar se existir, criar se não existir)
fileWrite, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
fmt.Println("Erro ao abrir para escrita:", err)
return
}
defer fileWrite.Close()
fmt.Println("Arquivo 'log.txt' aberto para escrita (truncado/criado).")
// Abrir para escrita (apenas adicionar, criar se não existir)
fileAppend, err := os.OpenFile("historico.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Erro ao abrir para adição:", err)
return
}
defer fileAppend.Close()
fmt.Println("Arquivo 'historico.txt' aberto para adição.")
}
Explicação dos flags:
os.O_RDONLY: Abre o arquivo somente para leitura.os.O_WRONLY: Abre o arquivo somente para escrita.os.O_RDWR: Abre o arquivo para leitura e escrita.os.O_APPEND: Adiciona dados ao final do arquivo.os.O_CREATE: Cria o arquivo se ele não existir.os.O_TRUNC: Trunca o arquivo (limpa o conteúdo) se ele já existir e for aberto para escrita.os.O_EXCL: Usado comos.O_CREATE, retorna um erro se o arquivo já existir (garante criação exclusiva).
6. Adicionando Conteúdo a Arquivos (Append)
Para adicionar conteúdo ao final de um arquivo existente, use os.OpenFile() com a flag os.O_APPEND.
package main
import (
"fmt"
"os"
)
func main() {
// Criar ou garantir que o arquivo exista
file, err := os.OpenFile("log_eventos.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("Erro ao abrir/criar o arquivo:", err)
return
}
defer file.Close()
_, err = file.WriteString("Evento: Usuário logado.\n")
if err != nil {
fmt.Println("Erro ao adicionar conteúdo:", err)
return
}
fmt.Println("Conteúdo adicionado a 'log_eventos.txt'.")
_, err = file.WriteString("Evento: Item adicionado ao carrinho.\n")
if err != nil {
fmt.Println("Erro ao adicionar conteúdo:", err)
return
}
fmt.Println("Mais conteúdo adicionado a 'log_eventos.txt'.")
}
7. Obtendo Informações do Arquivo (os.Stat())
os.Stat() retorna um os.FileInfo que contém metadados sobre o arquivo ou diretório.
package main
import (
"fmt"
"os"
"time"
)
func main() {
// Criar um arquivo para obter informações
err := os.WriteFile("info.txt", []byte("Algum texto."), 0644)
if err != nil {
fmt.Println("Erro ao preparar o arquivo:", err)
return
}
info, err := os.Stat("info.txt")
if err != nil {
fmt.Println("Erro ao obter informações do arquivo:", err)
return
}
fmt.Println("Informações do arquivo 'info.txt':")
fmt.Printf("Nome: %s\n", info.Name())
fmt.Printf("Tamanho: %d bytes\n", info.Size())
fmt.Printf("É um diretório? %t\n", info.IsDir())
fmt.Printf("Modo de permissão: %s\n", info.Mode())
fmt.Printf("Última modificação: %s\n", info.ModTime().Format(time.RFC1123))
fmt.Printf("Sistema (dependente do SO): %+v\n", info.Sys()) // Informações específicas do sistema operacional
}
8. Deletando Arquivos (os.Remove())
Para remover um arquivo ou diretório vazio.
package main
import (
"fmt"
"os"
)
func main() {
// Criar um arquivo para ser removido
err := os.WriteFile("para_remover.txt", []byte("Este arquivo será deletado."), 0644)
if err != nil {
fmt.Println("Erro ao preparar o arquivo:", err)
return
}
fmt.Println("Arquivo 'para_remover.txt' criado.")
// Remover o arquivo
err = os.Remove("para_remover.txt")
if err != nil {
fmt.Println("Erro ao remover o arquivo:", err)
return
}
fmt.Println("Arquivo 'para_remover.txt' removido com sucesso!")
// Tentar remover um arquivo que não existe (resultará em erro)
err = os.Remove("nao_existe.txt")
if err != nil {
fmt.Println("Erro ao tentar remover 'nao_existe.txt':", err) // Saída esperada: "remove nao_existe.txt: no such file or directory"
}
}
9. Renomeando/Movendo Arquivos (os.Rename())
os.Rename() pode ser usado para renomear um arquivo ou movê-lo para um novo local no mesmo sistema de arquivos.
package main
import (
"fmt"
"os"
)
func main() {
// Criar um arquivo para renomear
err := os.WriteFile("antigo_nome.txt", []byte("Conteúdo do arquivo antigo."), 0644)
if err != nil {
fmt.Println("Erro ao preparar o arquivo:", err)
return
}
fmt.Println("Arquivo 'antigo_nome.txt' criado.")
// Renomear o arquivo
err = os.Rename("antigo_nome.txt", "novo_nome.txt")
if err != nil {
fmt.Println("Erro ao renomear o arquivo:", err)
return
}
fmt.Println("Arquivo renomeado de 'antigo_nome.txt' para 'novo_nome.txt'.")
// Mover o arquivo para um subdiretório (se o diretório existir)
// Primeiro, criar o diretório
err = os.Mkdir("subdiretorio", 0755)
if err != nil && !os.IsExist(err) { // Ignorar erro se o diretório já existir
fmt.Println("Erro ao criar subdiretorio:", err)
return
}
err = os.Rename("novo_nome.txt", "subdiretorio/arquivo_movido.txt")
if err != nil {
fmt.Println("Erro ao mover o arquivo:", err)
return
}
fmt.Println("Arquivo 'novo_nome.txt' movido para 'subdiretorio/arquivo_movido.txt'.")
// Limpeza
os.RemoveAll("subdiretorio")
}
10. Trabalhando com Diretórios
10.1. Criando Diretórios (os.Mkdir(), os.MkdirAll())
os.Mkdir(path, perm): Cria um único diretório. Retorna um erro se o diretório pai não existir.os.MkdirAll(path, perm): Cria um diretório e quaisquer diretórios pais necessários.
package main
import (
"fmt"
"os"
)
func main() {
// Criar um único diretório
err := os.Mkdir("meu_diretorio", 0755)
if err != nil {
if os.IsExist(err) {
fmt.Println("Diretório 'meu_diretorio' já existe.")
} else {
fmt.Println("Erro ao criar 'meu_diretorio':", err)
return
}
} else {
fmt.Println("Diretório 'meu_diretorio' criado.")
}
// Criar diretórios aninhados
err = os.MkdirAll("pasta/subpasta/final", 0755)
if err != nil {
if os.IsExist(err) {
fmt.Println("Diretório 'pasta/subpasta/final' já existe.")
} else {
fmt.Println("Erro ao criar 'pasta/subpasta/final':", err)
return
}
} else {
fmt.Println("Diretórios 'pasta/subpasta/final' criados.")
}
// Limpeza
os.RemoveAll("meu_diretorio")
os.RemoveAll("pasta")
}
10.2. Removendo Diretórios (os.Remove(), os.RemoveAll())
os.Remove(path): Remove um diretório vazio.os.RemoveAll(path): Remove um diretório e todo o seu conteúdo (arquivos e subdiretórios). Use com cautela!
package main
import (
"fmt"
"os"
)
func main() {
// Criar diretório vazio para remover com os.Remove
os.Mkdir("diretorio_vazio", 0755)
fmt.Println("Diretório 'diretorio_vazio' criado.")
err := os.Remove("diretorio_vazio")
if err != nil {
fmt.Println("Erro ao remover 'diretorio_vazio':", err)
} else {
fmt.Println("Diretório 'diretorio_vazio' removido.")
}
// Criar diretório com conteúdo para remover com os.RemoveAll
os.MkdirAll("diretorio_com_conteudo/sub", 0755)
os.WriteFile("diretorio_com_conteudo/arquivo.txt", []byte("teste"), 0644)
fmt.Println("Diretório 'diretorio_com_conteudo' e seu conteúdo criados.")
err = os.RemoveAll("diretorio_com_conteudo")
if err != nil {
fmt.Println("Erro ao remover 'diretorio_com_conteudo':", err)
} else {
fmt.Println("Diretório 'diretorio_com_conteudo' e seu conteúdo removidos.")
}
}
11. Tratamento de Erros
Em Go, o tratamento de erros é explícito. Quase todas as funções de manipulação de arquivos retornam um error como segundo valor. É crucial verificar esses erros para garantir a robustez da sua aplicação.
package main
import (
"fmt"
"os"
)
func main() {
// Exemplo de tratamento de erro ao abrir um arquivo que não existe
_, err := os.Open("arquivo_inexistente.txt")
if err != nil {
if os.IsNotExist(err) {
fmt.Println("Erro: O arquivo não existe.")
} else {
fmt.Println("Erro inesperado ao abrir o arquivo:", err)
}
}
// Exemplo de tratamento de erro ao tentar escrever em um arquivo somente leitura
// Primeiro, crie um arquivo e defina-o como somente leitura
os.WriteFile("somente_leitura.txt", []byte(""), 0444) // 0444 = somente leitura para todos
file, err := os.OpenFile("somente_leitura.txt", os.O_WRONLY, 0644)
if err != nil {
// No Windows, pode ser "Access is denied."
// No Linux, pode ser "permission denied"
fmt.Println("Erro ao tentar abrir para escrita um arquivo somente leitura:", err)
} else {
defer file.Close()
_, err = file.WriteString("Tentando escrever.")
if err != nil {
fmt.Println("Erro ao escrever em arquivo somente leitura:", err)
}
}
os.Remove("somente_leitura.txt") // Limpeza
}
Funções úteis para tratamento de erros do os:
os.IsExist(err): Retornatruese o erro indica que um arquivo ou diretório já existe.os.IsNotExist(err): Retornatruese o erro indica que um arquivo ou diretório não existe.os.IsPermission(err): Retornatruese o erro indica uma permissão negada.
Conclusão
A manipulação de arquivos em Go é poderosa e flexível, principalmente através do pacote os. Compreender as diferentes funções e como lidar com erros é essencial para construir aplicações robustas que interagem com o sistema de arquivos. Lembre-se sempre de fechar os arquivos abertos usando defer file.Close() para evitar vazamento de recursos.