File I/O: Reading and Writing Files in Go
File input/output is a fundamental operation in most applications. Go provides a comprehensive set of tools in the os and io packages for working with files. This guide covers everything you need to know about file operations in Go.
Basic File Operations
Reading Files
package main
import (
"fmt"
"io/ioutil"
"log"
)
func main() {
// Read entire file into memory
data, err := ioutil.ReadFile("example.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println("File contents:")
fmt.Println(string(data))
}
Writing Files
package main
import (
"io/ioutil"
"log"
)
func main() {
content := []byte("Hello, World!\n")
// Write to file (creates or truncates)
err := ioutil.WriteFile("output.txt", content, 0644)
if err != nil {
log.Fatal(err)
}
println("File written successfully")
}
Appending to Files
package main
import (
"log"
"os"
)
func main() {
// Open file for appending
file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Write to file
_, err = file.WriteString("New log entry\n")
if err != nil {
log.Fatal(err)
}
println("Appended successfully")
}
Working with File Handles
Opening and Closing Files
package main
import (
"log"
"os"
)
func main() {
// Open file for reading
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Get file info
info, err := file.Stat()
if err != nil {
log.Fatal(err)
}
println("File name:", info.Name())
println("File size:", info.Size())
println("Is directory:", info.IsDir())
}
Reading Line by Line
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNum := 1
for scanner.Scan() {
fmt.Printf("%d: %s\n", lineNum, scanner.Text())
lineNum++
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
Reading in Chunks
package main
import (
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Read in 1KB chunks
buffer := make([]byte, 1024)
for {
n, err := file.Read(buffer)
if err != nil {
if err.Error() != "EOF" {
log.Fatal(err)
}
break
}
fmt.Printf("Read %d bytes: %s\n", n, string(buffer[:n]))
}
}
Advanced File Operations
Creating Directories
package main
import (
"log"
"os"
)
func main() {
// Create single directory
err := os.Mkdir("mydir", 0755)
if err != nil {
log.Fatal(err)
}
// Create nested directories
err = os.MkdirAll("path/to/nested/dir", 0755)
if err != nil {
log.Fatal(err)
}
println("Directories created")
}
Listing Directory Contents
package main
import (
"fmt"
"log"
"os"
)
func main() {
// List directory contents
entries, err := os.ReadDir(".")
if err != nil {
log.Fatal(err)
}
for _, entry := range entries {
if entry.IsDir() {
fmt.Printf("[DIR] %s\n", entry.Name())
} else {
info, _ := entry.Info()
fmt.Printf("[FILE] %s (%d bytes)\n", entry.Name(), info.Size())
}
}
}
Copying Files
package main
import (
"io"
"log"
"os"
)
func copyFile(src, dst string) error {
// Open source file
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
// Create destination file
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
// Copy contents
_, err = io.Copy(destination, source)
return err
}
func main() {
err := copyFile("source.txt", "destination.txt")
if err != nil {
log.Fatal(err)
}
println("File copied successfully")
}
Deleting Files and Directories
package main
import (
"log"
"os"
)
func main() {
// Delete file
err := os.Remove("file.txt")
if err != nil {
log.Fatal(err)
}
// Delete directory (must be empty)
err = os.Remove("emptydir")
if err != nil {
log.Fatal(err)
}
// Delete directory and contents
err = os.RemoveAll("dir")
if err != nil {
log.Fatal(err)
}
println("Files/directories deleted")
}
Working with Paths
Path Operations
package main
import (
"fmt"
"path/filepath"
)
func main() {
// Join paths
path := filepath.Join("home", "user", "documents", "file.txt")
fmt.Println("Joined path:", path)
// Get directory
dir := filepath.Dir(path)
fmt.Println("Directory:", dir)
// Get filename
file := filepath.Base(path)
fmt.Println("Filename:", file)
// Get extension
ext := filepath.Ext(path)
fmt.Println("Extension:", ext)
// Get absolute path
abs, _ := filepath.Abs("file.txt")
fmt.Println("Absolute path:", abs)
// Clean path
clean := filepath.Clean("path/to/../file.txt")
fmt.Println("Cleaned path:", clean)
}
Walking Directory Trees
package main
import (
"fmt"
"log"
"os"
"path/filepath"
)
func main() {
// Walk directory tree
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
fmt.Printf("[DIR] %s\n", path)
} else {
fmt.Printf("[FILE] %s (%d bytes)\n", path, info.Size())
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
Practical Examples
Reading Configuration Files
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
)
func readConfig(filename string) (map[string]string, error) {
config := make(map[string]string)
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// Skip comments and empty lines
if strings.HasPrefix(line, "#") || strings.TrimSpace(line) == "" {
continue
}
// Parse key=value
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
config[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
return config, scanner.Err()
}
func main() {
config, err := readConfig("config.txt")
if err != nil {
log.Fatal(err)
}
for key, value := range config {
fmt.Printf("%s = %s\n", key, value)
}
}
Writing Log Files
package main
import (
"fmt"
"log"
"os"
"time"
)
func main() {
// Create or open log file
logFile, err := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer logFile.Close()
// Create logger
logger := log.New(logFile, "", log.LstdFlags)
// Log messages
logger.Println("Application started")
logger.Printf("Current time: %s\n", time.Now().Format(time.RFC3339))
logger.Println("Processing data...")
logger.Println("Application finished")
}
Batch File Processing
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
)
func processFiles(dir, ext string) error {
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Process only files with matching extension
if !info.IsDir() && strings.HasSuffix(path, ext) {
fmt.Printf("Processing: %s\n", path)
// Process file here
}
return nil
})
}
func main() {
err := processFiles(".", ".txt")
if err != nil {
log.Fatal(err)
}
}
Best Practices
โ Good Practices
// Always defer Close()
file, err := os.Open("file.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Check errors
data, err := ioutil.ReadFile("file.txt")
if err != nil {
log.Fatal(err)
}
// Use buffered I/O for large files
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// Process line
}
// Use filepath for cross-platform paths
path := filepath.Join("home", "user", "file.txt")
// Set appropriate permissions
os.WriteFile("file.txt", data, 0644)
โ Anti-Patterns
// Don't forget to close files
file, _ := os.Open("file.txt")
// Missing defer file.Close()
// Don't ignore errors
data, _ := ioutil.ReadFile("file.txt")
// Don't use string concatenation for paths
path := "home" + "/" + "user" + "/" + "file.txt"
// Don't read entire large files into memory
data, _ := ioutil.ReadFile("huge_file.bin")
Resources
- Go os Package Documentation
- Go io Package Documentation
- Go bufio Package Documentation
- Go filepath Package Documentation
Summary
Go provides powerful file I/O capabilities:
- Use
os.Open()for reading andos.Create()for writing - Always defer
Close()to ensure files are closed - Use buffered I/O for better performance with large files
- Use
filepathfor cross-platform path handling - Handle errors properly
- Use appropriate file permissions
With these tools, you can efficiently handle file operations in your Go applications.
Comments