Nesse post vou mostrar três formas diferentes para ler um diretório com Golang.
[SPOILER ALERT] No final desse post vou mostrar como eu apliquei um dos exemplos para poder ler um diretório que continha mais de 3.6 milhões de arquivos.
Vamos iniciar os exemplos utilizando a função ReadDir
do package ioutil
.
package main import ( "fmt" "io/ioutil" "log" ) func main() { files, err := ioutil.ReadDir("/tmp/") if err != nil { log.Fatal(err) } for _, file := range files { fmt.Println(file.Name(), file.IsDir()) } }
Ao executar esse programa, obtive o seguinte resultado.
com.adobe.AdobeIPCBroker.ctrl-temporin false com.apple.launchd.qaDrjAPaee true com.google.Keystone true drivefs_ipc.501 false drivefs_ipc.501_shell false powerlog true
Além de informações como Name()
e IsDir()
, a struct os.FileInfo
nos dá a possibilidade de obter informações sobre tamanho do arquivo, permissão e data da última modificação através dos métodos Size()
, Mode()
e ModTime()
.
A segunda forma para ler um diretório que quero mostrar, é utilizando a função filepath.Walk
. Essa função espera dois parâmetros, sendo o primeiro o diretório inicial e o segundo uma função com a assinatura func(path string, info os.FileInfo, err error) error
.
package main import ( "fmt" "os" "path/filepath" ) func main() { err := filepath.Walk("/tmp/", func(path string, info os.FileInfo, err error) error { if err != nil { fmt.Println(err) return err } fmt.Printf("dir: %v: nome: %s\\n", info.IsDir(), path) return nil }) if err != nil { fmt.Println(err) } }
Ao executar esse programa, obtive o seguinte resultado:
dir: true: nome: /tmp/com.google.Keystone dir: false: nome: /tmp/com.google.Keystone/.keystone_install_lock dir: false: nome: /tmp/com.google.Keystone/.keystone_system_install_lock dir: false: nome: /tmp/drivefs_ipc.501 dir: false: nome: /tmp/drivefs_ipc.501_shell dir: true: nome: /tmp/powerlog
Como podemos ver, diferente da primeira versão, a função filepath.Walk
navega de forma recursiva por todos os sub diretórios que ele encontrar.
Por último, vamos ver como ler um diretório utilizando o método os.File.Readdir
.
package main import ( "fmt" "os" ) func main() { f, err := os.Open("/tmp") if err != nil { fmt.Println(err) return } files, err := f.Readdir(0) if err != nil { fmt.Println(err) return } for _, v := range files { fmt.Println(v.Name(), v.IsDir()) } }
Ao executar obtive o seguinte resultado:
com.adobe.AdobeIPCBroker.ctrl-temporin false drivefs_ipc.501_shell false com.google.Keystone true drivefs_ipc.501 false powerlog true com.apple.launchd.qaDrjAPaee true
Olhando para nossa última implementação, notamos que é feito um os.Open
antes de fazer a leitura do diretório, o que só irá ocorrer quando f.Readdir(0)
for executado.
E é por causa do parâmetro que passamos no func (f *File) Readdir(n int) ([]FileInfo, error)
que conseguimos ler diretórios com grande volume de arquivos.
Isso por que diferente dos exemplos anteriores que sempre lêem o diretório inteiro antes de retornar, Readdir
só irá ter esse comportamento quando receber o parâmetro 0.
Caso passe um valor maior do que 0, em cada chamada do método ele irá ler e retornar esse número de arquivos. Sabendo disso, se fizermos uma pequena mudança no nosso programa, podemos ler diretórios com MILHÕES de arquivos sem esbarrar em problemas de memória.
package main import ( "fmt" "io" "os" ) func main() { f, err := os.Open("/tmp") if err != nil { fmt.Println(err) return } for { files, err := f.Readdir(1) if err != nil { if err == io.EOF { break } fmt.Println(err) continue } fmt.Println(files[0].Name(), files[0].IsDir()) } }
Qualquer dúvida, só deixar nos comentários.
Até a próxima!