Go by Example

File Paths

filepath.Join, filepath.Dir, filepath.Base, filepath.Ext, filepath.Abs, and filepath.WalkDir for directory traversal.

The path/filepath package manipulates file paths in an OS-aware way. It handles the separator difference between Unix (/) and Windows (\) so path logic is portable across platforms.

filepath.Join constructs a path from components, using the correct separator for the current OS. It also cleans redundant separators and . or .. components.

package main
 
import (
    "fmt"
    "path/filepath"
)
 
func main() {
    p := filepath.Join("dir", "subdir", "file.txt")
    fmt.Println(p)              // dir/subdir/file.txt
 
    fmt.Println(filepath.Dir(p))  // dir/subdir
    fmt.Println(filepath.Base(p)) // file.txt
    fmt.Println(filepath.Ext(p))  // .txt
}

filepath.Abs resolves a relative path to an absolute one using the current working directory. Useful when you need a canonical path to log, store in a database, or compare against other paths.

package main
 
import (
    "fmt"
    "path/filepath"
)
 
func main() {
    abs, err := filepath.Abs("config.yaml")
    if err != nil {
        panic(err)
    }
    fmt.Println(abs) // /home/user/project/config.yaml
 
    // filepath.Clean resolves . and .. without hitting the filesystem
    fmt.Println(filepath.Clean("a/b/../c")) // a/c
}

filepath.WalkDir traverses a directory tree, calling a function for every file and directory it encounters. It is faster than the older filepath.Walk because it does not stat each entry separately.

package main
 
import (
    "fmt"
    "io/fs"
    "path/filepath"
)
 
func main() {
    err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            return err
        }
        if d.IsDir() {
            fmt.Println("dir: ", path)
        } else {
            fmt.Println("file:", path)
        }
        return nil
    })
    if err != nil {
        panic(err)
    }
}

In production

Always use filepath.Join (not path.Join or string concatenation) to build file paths - filepath handles OS-specific separators, while path is for URL-style slash paths only. filepath.WalkDir (Go 1.16+) is faster than filepath.Walk because it receives the fs.DirEntry directly from the directory read, avoiding a separate stat syscall per entry; prefer it for directory traversal. To skip an entire subtree in WalkDir, return fs.SkipDir when the callback is called with a directory entry. When building paths from user-supplied input, validate that the resolved absolute path starts with a known prefix before accessing the file - otherwise a ../../../etc/passwd traversal bypasses the intended directory boundary. Use filepath.Glob for simple pattern matching on a single directory level; for recursive matching use WalkDir with an filepath.Match check inside the callback.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free