File I/O in Go

Go provides comprehensive support for file operations through the os, io, bufio, and ioutil packages. Understanding these packages is essential for working with files in Go applications.

Reading Files

Reading Entire Files

The simplest way to read a file:

package main

import (
    "fmt"
    "os"
)

func main() {
    // Read entire file at once
    data, err := os.ReadFile("example.txt")
    if err != nil {
        fmt.Println("Error reading file:", err)
        return
    }
    
    fmt.Println("File contents:")
    fmt.Println(string(data))
}

For Go versions before 1.16, use ioutil.ReadFile:

import "io/ioutil"

// data, err := ioutil.ReadFile("example.txt")

Reading Files Line by Line

For large files, read line by line:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    lineNumber := 1
    
    for scanner.Scan() {
        fmt.Printf("Line %d: %s\n", lineNumber, scanner.Text())
        lineNumber++
    }
    
    if err := scanner.Err(); err != nil {
        fmt.Println("Error reading file:", err)
    }
}

Reading with Buffer

Using a buffered reader for more control:

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()
    
    reader := bufio.NewReader(file)
    
    for {
        line, err := reader.ReadString('\n')
        if err != nil {
            if err == io.EOF {
                // Process last line if it doesn't end with newline
                if line != "" {
                    fmt.Print("Last line:", line)
                }
                break
            }
            fmt.Println("Error reading:", err)
            break
        }
        
        fmt.Print("Line:", line)
    }
}

Writing Files

Writing Entire Files

package main

import (
    "fmt"
    "os"
)

func main() {
    data := "Hello, World!\nThis is a test file."
    
    // Write to file (creates or truncates)
    err := os.WriteFile("output.txt", []byte(data), 0644)
    if err != nil {
        fmt.Println("Error writing file:", err)
        return
    }
    
    fmt.Println("File written successfully")
}

Writing with File Handle

For more control over writing:

package main

import (
    "fmt"
    "os"
)

func main() {
    // Create or truncate file
    file, err := os.Create("output.txt")
    if err != nil {
        fmt.Println("Error creating file:", err)
        return
    }
    defer file.Close()
    
    // Write data
    data := []byte("Hello, World!\n")
    _, err = file.Write(data)
    if err != nil {
        fmt.Println("Error writing to file:", err)
        return
    }
    
    // Write string
    _, err = file.WriteString("This is another line.\n")
    if err != nil {
        fmt.Println("Error writing string:", err)
        return
    }
    
    fmt.Println("Data written successfully")
}

Appending to Files

package main

import (
    "fmt"
    "os"
)

func main() {
    // Open file for appending (creates if doesn't exist)
    file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()
    
    // Append data
    logEntry := "New log entry at " + time.Now().Format(time.RFC3339) + "\n"
    _, err = file.WriteString(logEntry)
    if err != nil {
        fmt.Println("Error appending to file:", err)
        return
    }
    
    fmt.Println("Log entry appended")
}

Buffered Writing

Using bufio.Writer for efficient writing:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("buffered_output.txt")
    if err != nil {
        fmt.Println("Error creating file:", err)
        return
    }
    defer file.Close()
    
    // Create buffered writer
    writer := bufio.NewWriter(file)
    
    // Write multiple lines
    for i := 1; i <= 10; i++ {
        _, err := fmt.Fprintf(writer, "Line number %d\n", i)
        if err != nil {
            fmt.Println("Error writing line:", err)
            return
        }
    }
    
    // Flush buffer to ensure all data is written
    err = writer.Flush()
    if err != nil {
        fmt.Println("Error flushing buffer:", err)
        return
    }
    
    fmt.Println("Buffered writing completed")
}

Working with Directories

Creating Directories

package main

import (
    "fmt"
    "os"
)

func main() {
    // Create single directory
    err := os.Mkdir("newdir", 0755)
    if err != nil {
        fmt.Println("Error creating directory:", err)
        return
    }
    
    // Create nested directories
    err = os.MkdirAll("parent/child/grandchild", 0755)
    if err != nil {
        fmt.Println("Error creating directories:", err)
        return
    }
    
    fmt.Println("Directories created successfully")
}

Reading Directories

package main

import (
    "fmt"
    "os"
)

func main() {
    // Read directory contents
    entries, err := os.ReadDir(".")
    if err != nil {
        fmt.Println("Error reading directory:", err)
        return
    }
    
    fmt.Println("Directory contents:")
    for _, entry := range entries {
        info, err := entry.Info()
        if err != nil {
            continue
        }
        
        fileType := "file"
        if entry.IsDir() {
            fileType = "directory"
        }
        
        fmt.Printf("%s: %s (%d bytes)\n", fileType, entry.Name(), info.Size())
    }
}

Walking Directory Trees

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func visit(path string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }
    
    indent := strings.Repeat("  ", strings.Count(path, string(os.PathSeparator))-1)
    fmt.Printf("%s%s\n", indent, info.Name())
    
    return nil
}

func main() {
    root := "."
    
    err := filepath.Walk(root, visit)
    if err != nil {
        fmt.Println("Error walking directory:", err)
    }
}

File Information

Getting file metadata:

package main

import (
    "fmt"
    "os"
)

func main() {
    // Get file info
    info, err := os.Stat("example.txt")
    if err != nil {
        fmt.Println("Error getting file info:", err)
        return
    }
    
    fmt.Printf("Name: %s\n", info.Name())
    fmt.Printf("Size: %d bytes\n", info.Size())
    fmt.Printf("Mode: %s\n", info.Mode())
    fmt.Printf("Modified: %s\n", info.ModTime())
    fmt.Printf("Is directory: %t\n", info.IsDir())
}

Copying Files

package main

import (
    "fmt"
    "io"
    "os"
)

func copyFile(src, dst string) error {
    sourceFile, err := os.Open(src)
    if err != nil {
        return err
    }
    defer sourceFile.Close()
    
    destFile, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer destFile.Close()
    
    _, err = io.Copy(destFile, sourceFile)
    if err != nil {
        return err
    }
    
    // Copy permissions
    sourceInfo, err := os.Stat(src)
    if err != nil {
        return err
    }
    
    return os.Chmod(dst, sourceInfo.Mode())
}

func main() {
    err := copyFile("source.txt", "destination.txt")
    if err != nil {
        fmt.Println("Error copying file:", err)
        return
    }
    
    fmt.Println("File copied successfully")
}

Temporary Files

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    // Create temporary file
    tempFile, err := ioutil.TempFile("", "example_*.txt")
    if err != nil {
        fmt.Println("Error creating temp file:", err)
        return
    }
    defer os.Remove(tempFile.Name()) // Clean up
    defer tempFile.Close()
    
    // Write to temp file
    _, err = tempFile.WriteString("This is temporary data")
    if err != nil {
        fmt.Println("Error writing to temp file:", err)
        return
    }
    
    fmt.Printf("Temp file created: %s\n", tempFile.Name())
    
    // Create temporary directory
    tempDir, err := ioutil.TempDir("", "example_dir")
    if err != nil {
        fmt.Println("Error creating temp dir:", err)
        return
    }
    defer os.RemoveAll(tempDir) // Clean up
    
    fmt.Printf("Temp directory created: %s\n", tempDir)
}

Error Handling

Proper error handling for file operations:

package main

import (
    "fmt"
    "os"
)

func safeFileOperation(filename string) error {
    // Check if file exists
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        return fmt.Errorf("file %s does not exist", filename)
    }
    
    // Attempt to open file
    file, err := os.Open(filename)
    if err != nil {
        return fmt.Errorf("failed to open file %s: %w", filename, err)
    }
    defer file.Close()
    
    // Perform operations...
    
    return nil
}

func main() {
    err := safeFileOperation("nonexistent.txt")
    if err != nil {
        fmt.Println("Operation failed:", err)
    }
}

Best Practices

  1. Always close files: Use defer file.Close() immediately after opening
  2. Handle errors: Check for errors on every file operation
  3. Use appropriate permissions: 0644 for files, 0755 for directories
  4. Buffer I/O operations: Use bufio for better performance
  5. Check file existence: Before operations that require files to exist
  6. Clean up temporary files: Remove temp files when done
  7. Use relative paths carefully: Be aware of working directory
  8. Handle large files: Don’t read entire large files into memory
  9. Use os.OpenFile: For fine-grained control over file opening
  10. Consider atomic operations: For critical file updates

Go’s file I/O is straightforward but powerful. The standard library provides everything you need for most file operations, with good performance and error handling.

For more on JSON handling, check our JSON tutorial. If you need to work with time, see the time handling tutorial.

Last updated on