depth of field photography of file arrangement

Como listar arquivos de um diretório

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!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

Deixe uma resposta