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