Skip to main content
โšก Calmops

File I/O: Reading and Writing Files in Go

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

Summary

Go provides powerful file I/O capabilities:

  • Use os.Open() for reading and os.Create() for writing
  • Always defer Close() to ensure files are closed
  • Use buffered I/O for better performance with large files
  • Use filepath for 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