Sitemap
Andrei Boar

Andrei Boar

Go Software Engineer based in Cluj-Napoca https://www.linkedin.com/in/andrei-boar/

List all files in a directory in Go

3 min readJul 10, 2024

--

Yesterday, I was working on a small script that was supposed to list all .md files inside a directory.

Press enter or click to view image in full size

Given this directory tree:

.
├── andrei.md
├── kata_one.md
├── kata_two.md
└── sub
├── another
│ └── kata_four.md
└── kata_three.md

3 directories, 4 files

I wanted a []string containing the absolute path of each .md file found inside my directory as below:

/home/andrei/Projects/katas/kata_one.md
/home/andrei/Projects/katas/kata_two.md
/home/andrei/Projects/katas/sub/another/kata_four.md
/home/andrei/Projects/katas/sub/kata_three.m

You’ll notice andrei.md is missing from the list because andrei.md is a directory, not a file.

Let’s see what options we have to achieve this result.

Using fs.Glob: search by pattern

Since I need all .md files under dir, I can create a filesystem for the tree of files rooted inside dir by using os.DirFS and then do a pattern match for *.md files with fs.Glob:

package main

import (
"fmt"
"io/fs"
"log"
"os"
"path"
)

func main() {
dir := "/home/andrei/Projects/katas"

files := listFiles(dir)

for _, v := range files {
fmt.Println(v)
}
}

func listFiles(dir string) []string {
root := os.DirFS(dir)

mdFiles, err := fs.Glob(root, "*.md")

if err != nil {
log.Fatal(err)
}

var files []string
for _, v := range mdFiles {
files = append(files, path.Join(dir, v))
}
return files
}

Using this approach, I get the following output:

/home/andrei/Projects/katas/andrei.md
/home/andrei/Projects/katas/kata_one.md
/home/andrei/Projects/katas/kata_two.md

An approach with the same result would be to use filepath.Glob directly:

func listFiles(dir string) []string {
entries, err := filepath.Glob(dir + "/*.md")
if err != nil {
log.Fatal(err)
}

var files []string
for _, v := range entries {
files = append(files, filepath.Join(dir, v))
}

return files
}

There are 2 problems with these approaches:

  1. It only lists entries from the current directory. It doesn’t search for files inside child directories.
  2. In my case /home/andrei/Projects/katas/andrei.md is a directory path, not a file path. Since I don’t need directories I need to filter them out.

Let’s see another example where we can filter out directories.

Using os.ReadDir: list all directory entries

Here’s an example using os.ReadDir which reads the named directory, returning all its entries([]DirEntry) sorted by filename. A DirEntry is an interface with a small number of utility methods.

package main

import (
"fmt"
"log"
"os"
"path/filepath"
)

func main() {
dir := "/home/andrei/Projects/katas"

files := listFiles(dir)

for _, v := range files {
fmt.Println(v)
}
}

func listFiles(dir string) []string {
entries, err := os.ReadDir(dir)
if err != nil {
log.Fatal(err)
}

var files []string
for _, v := range entries {
if v.IsDir() {
continue
}
if filepath.Ext(v.Name()) != ".md" {
continue
}
files = append(files, filepath.Join(dir, v.Name()))
}

return files
}

We can check if an entry is a directory with v.IsDir(). So we can filter out the andrei.md directory, getting this result:

/home/andrei/Projects/katas/kata_one.md
/home/andrei/Projects/katas/kata_two.md

Another function worth mentioning filepath.Ext() which is used to check the filename extension. Don’t forget to add the dot in the extension: checking for md instead of .md will not work!

Get Andrei Boar’s stories in your inbox

Join Medium for free to get updates from this writer.

The directory is filtered out, but we still miss files from the subdirectories. Let’s see how to fix that in the next section.

Using filePath.WalkDir: recursive search

WalkDir recursively walks the directory tree, thus checking child directories and files. For each entry in the directory, it calls a function in which we can filter out what we don’t need and add the files to the result:

package main

import (
"fmt"
"io/fs"
"log"
"path/filepath"
)

func main() {
dir := "/home/andrei/Projects/katas"

files := listFiles(dir)

for _, v := range files {
fmt.Println(v)
}
}

func listFiles(dir string) []string {
var files []string

err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() && filepath.Ext(path) == ".md" {
files = append(files, path)
}
return nil
})
if err != nil {
log.Fatal(err)
}

return files
}

Now we get the intended result:

/home/andrei/Projects/katas/kata_one.md
/home/andrei/Projects/katas/kata_two.md
/home/andrei/Projects/katas/sub/another/kata_four.md
/home/andrei/Projects/katas/sub/kata_three.md

Conclusion

Here’s a quick guideline on when to use which:

  • use os.ReadDir when you quickly want to list direct entries inside a directory.
  • use fs.Glob when you need to list files with the same extension and you’re 100% sure you don’t have directories named with that extension.
  • use filePath.WalkDir when you want to recursively search inside a directory tree.

That’s all!

If you learned something new please give a 👏 or bookmark this article for when you need to list some files. If I missed something feel free to leave a comment or connect with me on Linkedin.

Happy coding!

--

--