Arquivos
A linguagem Go fornece as seguintes bibliotecas padrão para processamento de arquivos:
- Biblioteca
os, responsável pela implementação concreta da interação com o sistema de arquivos do SO - Biblioteca
io, camada de abstração para leitura e escrita de IO - Biblioteca
fs, camada de abstração para sistemas de arquivos
Este artigo explicará como realizar o processamento básico de arquivos usando Go.
Abrir
As duas formas comuns de abrir arquivos são usando duas funções fornecidas pelo pacote os. A função Open retorna um ponteiro de arquivo e um erro,
func Open(name string) (*File, error)A segunda, OpenFile, oferece um controle mais granular. A função Open é apenas um encapsulamento simples da função OpenFile.
func OpenFile(name string, flag int, perm FileMode) (*File, error)Vamos primeiro apresentar o primeiro método de uso, bastando fornecer o nome do arquivo correspondente, como no código abaixo
func main() {
file, err := os.Open("README.txt")
fmt.Println(file, err)
}O caminho de busca do arquivo é, por padrão, o caminho onde o arquivo go.mod do projeto está localizado. Como não existe o arquivo README.txt no projeto, naturalmente retornará um erro.
<nil> open README.txt: The system cannot find the file specified.Como existem muitos tipos de erros de IO, é necessário verificar manualmente se o arquivo existe. O pacote os também fornece funções convenientes para isso. O código modificado fica assim
func main() {
file, err := os.Open("README.txt")
if os.IsNotExist(err) {
fmt.Println("Arquivo não existe")
} else if err != nil {
fmt.Println("Erro de acesso ao arquivo")
} else {
fmt.Println("Arquivo lido com sucesso", file)
}
}Executando novamente, a saída é
Arquivo não existeNa verdade, a primeira função abre o arquivo apenas para leitura, não podendo ser modificado
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}Através da função OpenFile, é possível controlar mais detalhes, como modificar o descritor de arquivo e as permissões do arquivo. Sobre os descritores de arquivo, o pacote os fornece as seguintes constantes para uso.
const (
// Somente leitura, somente escrita, leitura e escrita - deve-se especificar um dos três
O_RDONLY int = syscall.O_RDONLY // Abre o arquivo em modo somente leitura
O_WRONLY int = syscall.O_WRONLY // Abre o arquivo em modo somente escrita
O_RDWR int = syscall.O_RDWR // Abre o arquivo em modo leitura e escrita
// Os valores restantes são usados para controlar o comportamento
O_APPEND int = syscall.O_APPEND // Ao escrever no arquivo, adiciona dados ao final do arquivo
O_CREATE int = syscall.O_CREAT // Cria o arquivo se não existir
O_EXCL int = syscall.O_EXCL // Usado com O_CREATE, o arquivo não deve existir
O_SYNC int = syscall.O_SYNC // Abre o arquivo em modo de IO síncrono
O_TRUNC int = syscall.O_TRUNC // Trunca o arquivo gravável ao abrir
)Sobre as permissões de arquivo, são fornecidas as seguintes constantes.
const (
ModeDir = fs.ModeDir // d: diretório
ModeAppend = fs.ModeAppend // a: somente adição
ModeExclusive = fs.ModeExclusive // l: exclusivo
ModeTemporary = fs.ModeTemporary // T: arquivo temporário
ModeSymlink = fs.ModeSymlink // L: link simbólico
ModeDevice = fs.ModeDevice // D: arquivo de dispositivo
ModeNamedPipe = fs.ModeNamedPipe // p: pipe nomeado (FIFO)
ModeSocket = fs.ModeSocket // S: socket de domínio Unix
ModeSetuid = fs.ModeSetuid // u: setuid
ModeSetgid = fs.ModeSetgid // g: setgid
ModeCharDevice = fs.ModeCharDevice // c: dispositivo de caractere Unix, requer que ModeDevice esteja definido
ModeSticky = fs.ModeSticky // t: sticky bit
ModeIrregular = fs.ModeIrregular // ?: arquivo irregular
// Máscara de bits de tipo. Para arquivos regulares, nada é definido.
ModeType = fs.ModeType
ModePerm = fs.ModePerm // Bits de permissão Unix, 0o777
)Abaixo está um exemplo de código que abre um arquivo em modo de leitura e escrita, com permissão 0666, o que significa que todos podem ler e escrever neste arquivo, e ele será criado automaticamente se não existir.
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if os.IsNotExist(err) {
fmt.Println("Arquivo não existe")
} else if err != nil {
fmt.Println("Erro de acesso ao arquivo")
} else {
fmt.Println("Arquivo aberto com sucesso", file.Name())
file.Close()
}
}Saída
Arquivo aberto com sucesso README.txtSe você apenas quiser obter algumas informações sobre o arquivo sem lê-lo, pode usar a função os.Stat(). Um exemplo de código é mostrado abaixo
func main() {
fileInfo, err := os.Stat("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(fmt.Sprintf("%+v", fileInfo))
}
}Saída
&{name:README.txt FileAttributes:32 CreationTime:{LowDateTime:3603459389 HighDateTime:31016791} LastAccessTime:{LowDateTime:3603459389 HighDateTime:31016791} LastWriteTime:{LowDateTime:3603459389 HighDateTime:31016791} FileSizeHigh
:0 FileSizeLow:0 Reserved0:0 filetype:0 Mutex:{state:0 sema:0} path:README.txt vol:0 idxhi:0 idxlo:0 appendNameToPath:false}WARNING
Após abrir um arquivo, lembre-se sempre de fechá-lo. Geralmente, a operação de fechamento é colocada em uma instrução defer
defer file.Close()Ler
Após abrir o arquivo com sucesso, você pode realizar operações de leitura. Sobre as operações de leitura de arquivos, o tipo *os.File fornece os seguintes métodos públicos
// Lê o arquivo para o slice de bytes fornecido
func (f *File) Read(b []byte) (n int, err error)
// Comparado ao primeiro, pode ler a partir de um offset especificado
func (f *File) ReadAt(b []byte, off int64) (n int, err error)Na maioria dos casos, o primeiro é mais usado. Para o primeiro método, você precisa escrever sua própria lógica para a expansão dinâmica do slice durante a leitura. O código é mostrado abaixo
func ReadFile(file *os.File) ([]byte, error) {
buffer := make([]byte, 0, 512)
for {
// Quando a capacidade é insuficiente
if len(buffer) == cap(buffer) {
// Expande
buffer = append(buffer, 0)[:len(buffer)]
}
// Continua lendo o arquivo
offset, err := file.Read(buffer[len(buffer):cap(buffer)])
// Inclui os dados escritos no slice
buffer = buffer[:len(buffer)+offset]
// Quando ocorre um erro
if err != nil {
if errors.Is(err, io.EOF) {
err = nil
}
return buffer, err
}
}
}A lógica restante é mostrada abaixo
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("Erro de acesso ao arquivo")
} else {
fmt.Println("Arquivo aberto com sucesso", file.Name())
bytes, err := ReadFile(file)
if err != nil {
fmt.Println("Erro de leitura do arquivo", err)
} else {
fmt.Println(string(bytes))
}
file.Close()
}
}Saída
Arquivo aberto com sucesso README.txt
hello world!Além disso, você pode usar duas funções convenientes para leitura de arquivos: a função ReadFile do pacote os e a função ReadAll do pacote io. Para os.ReadFile, basta fornecer o caminho do arquivo, enquanto para io.ReadAll, você precisa fornecer uma implementação do tipo io.Reader.
os.ReadFile
func ReadFile(name string) ([]byte, error)Exemplo de uso
func main() {
bytes, err := os.ReadFile("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(bytes))
}
}Saída
hello world!io.ReadAll
func ReadAll(r Reader) ([]byte, error)Exemplo de uso
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("Erro de acesso ao arquivo")
} else {
fmt.Println("Arquivo aberto com sucesso", file.Name())
bytes, err := io.ReadAll(file)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(bytes))
}
file.Close()
}
}Arquivo aberto com sucesso README.txt
hello world!Escrever
A estrutura os.File fornece os seguintes métodos para escrever dados
// Escreve um slice de bytes
func (f *File) Write(b []byte) (n int, err error)
// Escreve uma string
func (f *File) WriteString(s string) (n int, err error)
// Escreve a partir de uma posição especificada, retorna erro quando aberto em modo os.O_APPEND
func (f *File) WriteAt(b []byte, off int64) (n int, err error)Se você quiser escrever dados em um arquivo, deve abri-lo em modo O_WRONLY ou O_RDWR, caso contrário não conseguirá escrever no arquivo com sucesso. Abaixo está um exemplo de abertura de arquivo em modo os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC com permissão 0666 para escrever dados
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
if err != nil {
fmt.Println("Erro de acesso ao arquivo")
} else {
fmt.Println("Arquivo aberto com sucesso", file.Name())
for i := 0; i < 5; i++ {
offset, err := file.WriteString("hello world!\n")
if err != nil {
fmt.Println(offset, err)
}
}
fmt.Println(file.Close())
}
}Como o arquivo foi aberto em modo os.O_APPEND, ao escrever no arquivo os dados serão adicionados ao final. Após a execução, o conteúdo do arquivo é
hello world!
hello world!
hello world!
hello world!
hello world!Escrever um slice de bytes em um arquivo é uma operação similar, então não entraremos em detalhes. Para operações de escrita em arquivos, a biblioteca padrão também fornece funções convenientes: os.WriteFile e io.WriteString
os.WriteFile
func WriteFile(name string, data []byte, perm FileMode) errorExemplo de uso
func main() {
err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666)
if err != nil {
fmt.Println(err)
}
}Neste momento, o conteúdo do arquivo é
hello world!io.WriteString
func WriteString(w Writer, s string) (n int, err error)Exemplo de uso
func main() {
file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
if err != nil {
fmt.Println("Erro de acesso ao arquivo")
} else {
fmt.Println("Arquivo aberto com sucesso", file.Name())
for i := 0; i < 5; i++ {
offset, err := io.WriteString(file, "hello world!\n")
if err != nil {
fmt.Println(offset, err)
}
}
fmt.Println(file.Close())
}
}hello world!
hello world!
hello world!
hello world!
hello world!A função os.Create é usada para criar arquivos e é essencialmente um encapsulamento de OpenFile.
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}WARNING
Ao criar um arquivo, se o diretório pai não existir, a criação falhará e retornará um erro.
Copiar
Para copiar arquivos, você precisa abrir dois arquivos ao mesmo tempo. O primeiro método é ler os dados do arquivo original e depois escrevê-los no arquivo de destino. Um exemplo de código é mostrado abaixo
func main() {
// Lê dados do arquivo original
data, err := os.ReadFile("README.txt")
if err != nil {
fmt.Println(err)
return
}
// Escreve no arquivo de destino
err = os.WriteFile("README(1).txt", data, 0666)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Cópia realizada com sucesso")
}
}*os.File.ReadFrom
Outro método é usar o método ReadFrom fornecido por os.File. Ao abrir os arquivos, um é somente leitura e outro é somente escrita.
func (f *File) ReadFrom(r io.Reader) (n int64, err error)Exemplo de uso
func main() {
// Abre o arquivo original em modo somente leitura
origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
if err != nil {
fmt.Println(err)
return
}
defer origin.Close()
// Abre o arquivo de cópia em modo somente escrita
target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Println(err)
return
}
defer target.Close()
// Lê dados do arquivo original e escreve no arquivo de cópia
offset, err := target.ReadFrom(origin)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Arquivo copiado com sucesso", offset)
}Este método de cópia precisa primeiro ler todo o conteúdo do arquivo fonte para a memória e depois escrevê-lo no arquivo de destino. Não é recomendado para arquivos muito grandes.
io.Copy
Outro método é usar a função io.Copy, que lê e escreve ao mesmo tempo, primeiro lendo o conteúdo para um buffer e depois escrevendo no arquivo de destino. O tamanho padrão do buffer é 32KB.
func Copy(dst Writer, src Reader) (written int64, err error)Exemplo de uso
func main() {
// Abre o arquivo original em modo somente leitura
origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
if err != nil {
fmt.Println(err)
return
}
defer origin.Close()
// Abre o arquivo de cópia em modo somente escrita
target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
fmt.Println(err)
return
}
defer target.Close()
// Copia
written, err := io.Copy(target, origin)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(written)
}
}Você também pode usar io.CopyBuffer para especificar o tamanho do buffer.
Renomear
Renomear também pode ser entendido como mover arquivos. Você usará a função Rename do pacote os.
func Rename(oldpath, newpath string) errorExemplo
func main() {
err := os.Rename("README.txt", "readme.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Renomeado com sucesso")
}
}Esta função tem o mesmo efeito para pastas.
Excluir
A operação de exclusão é muito mais simples do que outras operações, usando apenas duas funções do pacote os
// Exclui um único arquivo ou diretório vazio, retorna erro quando o diretório não está vazio
func Remove(name string) error
// Exclui todos os arquivos e diretórios no diretório especificado, incluindo subdiretórios e subarquivos
func RemoveAll(path string) errorÉ muito simples de usar. Abaixo está um exemplo de exclusão de diretório
func main() {
// Exclui todos os arquivos e subdiretórios no diretório atual
err := os.RemoveAll(".")
if err != nil {
fmt.Println(err)
}else {
fmt.Println("Excluído com sucesso")
}
}Abaixo está um exemplo de exclusão de um único arquivo
func main() {
// Exclui todos os arquivos e subdiretórios no diretório atual
err := os.Remove("README.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Excluído com sucesso")
}
}Sincronizar
A função os.Sync encapsula a chamada de sistema subjacente Fsync, usada para gravar os dados de IO em cache no sistema operacional no disco
func main() {
create, err := os.Create("test.txt")
if err != nil {
panic(err)
}
defer create.Close()
_, err = create.Write([]byte("hello"))
if err != nil {
panic(err)
}
// Sincroniza com o disco
if err := create.Sync();err != nil {
return
}
}Pastas
Muitas operações de pastas são similares às operações de arquivos
Ler
Para pastas, existem duas formas de abertura
os.ReadDir
A primeira forma é usar a função os.ReadDir
func ReadDir(name string) ([]DirEntry, error)func main() {
// Diretório atual
dir, err := os.ReadDir(".")
if err != nil {
fmt.Println(err)
} else {
for _, entry := range dir {
fmt.Println(entry.Name())
}
}
}*os.File.ReadDir
A segunda forma é usar a função *os.File.ReadDir. os.ReadDir é essencialmente apenas um encapsulamento simples de *os.File.ReadDir.
// Quando n < 0, lê todo o conteúdo da pasta
func (f *File) ReadDir(n int) ([]DirEntry, error)func main() {
// Diretório atual
dir, err := os.Open(".")
if err != nil {
fmt.Println(err)
}
defer dir.Close()
dirs, err := dir.ReadDir(-1)
if err != nil {
fmt.Println(err)
} else {
for _, entry := range dirs {
fmt.Println(entry.Name())
}
}
}Criar
A operação de criação de pastas usa duas funções do pacote os
// Cria um diretório com o nome especificado e permissões especificadas
func Mkdir(name string, perm FileMode) error
// Comparado à anterior, esta função cria todos os diretórios pai necessários
func MkdirAll(path string, perm FileMode) errorExemplo
func main() {
err := os.Mkdir("src", 0666)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Criado com sucesso")
}
}Copiar
Podemos escrever nossa própria função para percorrer recursivamente toda a pasta, mas a biblioteca padrão filepath já fornece uma função com funcionalidade similar, então podemos usá-la diretamente. Um exemplo simples de código para copiar pastas é mostrado abaixo.
func CopyDir(src, dst string) error {
// Verifica o status da pasta de origem
_, err := os.Stat(src)
if err != nil {
return err
}
return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
// Calcula o caminho relativo
rel, err := filepath.Rel(src, path)
if err != nil {
return err
}
// Concatena o caminho de destino
destpath := filepath.Join(dst, rel)
// Cria a pasta
var dirpath string
var mode os.FileMode = 0755
if info.IsDir() {
dirpath = destpath
mode = info.Mode()
} else if info.Mode().IsRegular() {
dirpath = filepath.Dir(destpath)
}
if err := os.MkdirAll(dirpath, mode); err != nil {
return err
}
// Cria o arquivo
if info.Mode().IsRegular() {
srcfile, err := os.Open(path)
if err != nil {
return err
}
// Lembre-se sempre de fechar o arquivo
defer srcfile.Close()
destfile, err := os.OpenFile(destpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
if err != nil {
return err
}
defer destfile.Close()
// Copia o conteúdo do arquivo
if _, err := io.Copy(destfile, srcfile); err != nil {
return err
}
return nil
}
return nil
})
}filepath.Walk percorrerá recursivamente toda a pasta. Durante o processo, quando encontra uma pasta, cria a pasta; quando encontra um arquivo, cria um novo arquivo e copia. O código é um pouco mais extenso do que copiar arquivos, mas não é complexo.
