Files, Directories, and Embed in Go
Handling files and filesystems is central to backend development, CLI utilities, and containerized microservices. Go provides clean abstractions across the os, io, bufio, path/filepath, and embed packages.
1. Reading Files
Go offers three main approaches to reading files: one-shot reads, streaming chunk reads, and buffered readers.
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
filePath := "/tmp/example.txt"
_ = os.WriteFile(filePath, []byte("Line 1: Go Systems\nLine 2: Cloud Native\n"), 0644)
defer os.Remove(filePath)
// 1. One-shot read: Reads entire file into memory (good for small configs)
data, err := os.ReadFile(filePath)
if err != nil {
panic(err)
}
fmt.Printf("ReadFile (%d bytes):\n%s\n", len(data), string(data))
// 2. Open file for chunk-by-chunk or seekable streaming
file, err := os.Open(filePath)
if err != nil {
panic(err)
}
defer file.Close()
chunk := make([]byte, 8)
n, err := file.Read(chunk)
fmt.Printf("Read %d bytes into chunk: %q\n", n, string(chunk[:n]))
// 3. Buffered reader (avoids excessive syscalls)
_, _ = file.Seek(0, io.SeekStart) // rewind to start
reader := bufio.NewReader(file)
peeked, _ := reader.Peek(6)
fmt.Printf("Peeked first 6 bytes: %q\n", string(peeked))
}2. Writing Files
For writing, Go provides os.WriteFile, granular file creation with flags, and buffered writers.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
path := "/tmp/write_demo.txt"
defer os.Remove(path)
// 1. One-shot atomic-style write
err := os.WriteFile(path, []byte("initial line\n"), 0644)
if err != nil {
panic(err)
}
// 2. Open with flags for appending (os.O_APPEND|os.O_CREATE|os.O_WRONLY)
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer f.Close()
if _, err := f.WriteString("appended line 2\n"); err != nil {
panic(err)
}
// 3. Buffered Writer (flushes batched memory blocks to disk)
bufWriter := bufio.NewWriter(f)
_, _ = bufWriter.WriteString("buffered line 3\n")
_, _ = bufWriter.WriteString("buffered line 4\n")
_ = bufWriter.Flush() // Explicit flush ensures everything hits the OS page cache
content, _ := os.ReadFile(path)
fmt.Println("File contents:\n" + string(content))
}3. Line Filters and Scanners
A line filter is a common pattern for processing incoming text streams (e.g., parsing log files line-by-line without loading gigantic files into memory).
package main
import (
"bufio"
"fmt"
"strings"
)
func main() {
rawLogs := "INFO: server listening on :8080\nWARN: high memory utilization\nERROR: database timeout\n"
// bufio.Scanner breaks input into tokens (lines by default)
scanner := bufio.NewScanner(strings.NewReader(rawLogs))
lineCount := 0
for scanner.Scan() {
line := scanner.Text()
lineCount++
// Filter or transform lines
if strings.HasPrefix(line, "ERROR") {
fmt.Printf("[Alert Line %d] %s\n", lineCount, line)
}
}
if err := scanner.Err(); err != nil {
fmt.Printf("Error scanning input: %v\n", err)
}
}4. File Paths (path/filepath)
Use path/filepath instead of string concatenation to guarantee cross-platform compatibility across Linux, macOS, and Windows.
package main
import (
"fmt"
"path/filepath"
)
func main() {
// Join cleanly normalizes separators
p := filepath.Join("var", "log", "nginx", "access.log")
fmt.Println("Joined path:", p)
// Base, Dir, and Ext
fmt.Println("Dir: ", filepath.Dir(p)) // var/log/nginx
fmt.Println("Base:", filepath.Base(p)) // access.log
fmt.Println("Ext: ", filepath.Ext(p)) // .log
// Check if path is absolute
fmt.Println("IsAbs:", filepath.IsAbs(p)) // false
// Clean removes redundant dots and slashes
messy := "a/b/../c/./d/"
fmt.Println("Cleaned:", filepath.Clean(messy)) // a/c/d
// Relative path calculation
rel, _ := filepath.Rel("var/log", p)
fmt.Println("Rel to var/log:", rel) // nginx/access.log
}5. Directories and Recursive Traversal
package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
)
func main() {
baseDir, err := os.MkdirTemp("", "k8s-manifests-*")
if err != nil {
panic(err)
}
defer os.RemoveAll(baseDir) // recursive cleanup
// Create nested directories
subDir := filepath.Join(baseDir, "deploy", "overlays")
if err := os.MkdirAll(subDir, 0755); err != nil {
panic(err)
}
// Create dummy files
_ = os.WriteFile(filepath.Join(baseDir, "kustomization.yaml"), []byte("resources: []"), 0644)
_ = os.WriteFile(filepath.Join(subDir, "prod.yaml"), []byte("env: prod"), 0644)
// Read immediate directory entries (fast, non-recursive)
entries, _ := os.ReadDir(baseDir)
fmt.Println("Top-level directory entries:")
for _, entry := range entries {
fmt.Printf(" - %s (isDir: %t)\n", entry.Name(), entry.IsDir())
}
// Recursive traversal with WalkDir (efficient, passes DirEntry)
fmt.Println("\nRecursive WalkDir:")
err = filepath.WalkDir(baseDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
rel, _ := filepath.Rel(baseDir, path)
fmt.Printf("Walked: %s (dir: %t)\n", rel, d.IsDir())
return nil
})
if err != nil {
panic(err)
}
}6. Temporary Files and Directories
Temporary resources are vital for testing, caching, and building atomic file replacements.
package main
import (
"fmt"
"os"
)
func main() {
// os.CreateTemp accepts directory ("" uses system default /tmp) and a pattern
tmpFile, err := os.CreateTemp("", "worker-job-*.tmp")
if err != nil {
panic(err)
}
// Always schedule cleanup
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
fmt.Println("Created temp file:", tmpFile.Name())
_, _ = tmpFile.WriteString("intermediate calculation")
// Temporary directory
tmpDir, err := os.MkdirTemp("", "worker-bundle-*")
if err != nil {
panic(err)
}
defer os.RemoveAll(tmpDir)
fmt.Println("Created temp directory:", tmpDir)
}7. The embed Directive (//go:embed)
Go 1.16 introduced the embed directive, allowing static assets (HTML, SQL schemas, configuration files, images) to be baked directly into the compiled executable binary at build time.
package main
import (
"embed"
"fmt"
)
// Single file embedded as string or []byte
//go:embed sample_config.txt
var embeddedConfig string
// Multiple files or folder trees embedded as embed.FS
//go:embed static/*
var staticFS embed.FS
func main() {
// 1. Reading single embedded string
fmt.Println("Embedded single file content:", embeddedConfig)
// 2. Reading from embedded filesystem (implements io/fs.FS)
files, err := staticFS.ReadDir("static")
if err == nil {
for _, file := range files {
fmt.Printf("Embedded file: %s (size: %d)\n", file.Name(), file.Type())
}
}
// Read file contents directly from embed.FS
data, err := staticFS.ReadFile("static/index.html")
if err == nil {
fmt.Printf("index.html body: %s\n", string(data))
}
}TIP
embed.FSis read-only and thread-safe. You can pass it directly tohttp.FileServer(http.FS(staticFS))to serve frontend single-page apps (SPAs) directly from a zero-dependency Go single binary!
Related Topics
- Strings, Templates, and Regex — Text rendering and templating.
- Time, Numbers, URLs, and Crypto — Hashes, Base64, and string parsing.
- CLI, Environment, and Logging — Building full-featured command-line programs.