Skip to content

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,

go
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.

go
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

go
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

go
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 existe

Na verdade, a primeira função abre o arquivo apenas para leitura, não podendo ser modificado

go
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.

go
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.

go
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.

go
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.txt

Se 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

go
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

go
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

go
// 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

go
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

go
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

go
func ReadFile(name string) ([]byte, error)

Exemplo de uso

go
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

go
func ReadAll(r Reader) ([]byte, error)

Exemplo de uso

go
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

go
// 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

go
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 é

txt
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

go
func WriteFile(name string, data []byte, perm FileMode) error

Exemplo de uso

go
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 é

txt
hello world!

io.WriteString

go
func WriteString(w Writer, s string) (n int, err error)

Exemplo de uso

go
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.

go
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

go
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.

go
func (f *File) ReadFrom(r io.Reader) (n int64, err error)

Exemplo de uso

go
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.

go
func Copy(dst Writer, src Reader) (written int64, err error)

Exemplo de uso

go
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.

go
func Rename(oldpath, newpath string) error

Exemplo

go
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

go
// 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

go
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

go
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

go
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

go
func ReadDir(name string) ([]DirEntry, error)
go
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.

go
// Quando n < 0, lê todo o conteúdo da pasta
func (f *File) ReadDir(n int) ([]DirEntry, error)
go
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

go
// 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) error

Exemplo

go
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.

go
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.

Golang por www.golangdev.cn edit