Go File Handling

File handling covers reading data from files and writing data to files. Go provides the os package for low-level file operations and the bufio package for efficient buffered reading. The os package also provides a higher-level helper through os.ReadFile and os.WriteFile for simple cases.

File Operations Overview

File Operations in Go
├── Create a new file       os.Create()
├── Open an existing file   os.Open()
├── Read entire file        os.ReadFile()
├── Write entire file       os.WriteFile()
├── Read line by line       bufio.Scanner
├── Write with buffer       bufio.Writer
└── Close a file            file.Close()  ← always defer this

Writing to a File

Simple Write – os.WriteFile

package main

import (
    "fmt"
    "os"
)

func main() {
    content := []byte("Hello, File!\nWelcome to Go file handling.\n")

    err := os.WriteFile("notes.txt", content, 0644)
    if err != nil {
        fmt.Println("Error writing file:", err)
        return
    }
    fmt.Println("File written successfully")
}

The 0644 is a Unix permission code: owner can read and write; everyone else can read.

Reading from a File

Simple Read – os.ReadFile

package main

import (
    "fmt"
    "os"
)

func main() {
    data, err := os.ReadFile("notes.txt")
    if err != nil {
        fmt.Println("Error reading file:", err)
        return
    }
    fmt.Println(string(data))
}

Output:

Hello, File!
Welcome to Go file handling.

Reading a File Line by Line

For large files, reading line by line with bufio.Scanner is more memory-efficient than loading the whole file at once.

package main

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

func main() {
    file, err := os.Open("notes.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close() // always close the file

    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("Scanner error:", err)
    }
}

Output:

Line 1: Hello, File!
Line 2: Welcome to Go file handling.

File Read/Write Flow

WRITING:
Program → os.WriteFile() → creates/overwrites file on disk

READING (whole file):
File on disk → os.ReadFile() → []byte in memory → string(data)

READING (line by line):
File on disk → os.Open() → bufio.Scanner → one line at a time

Appending to a File

Open the file with the append and write-only flags to add content without overwriting.

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.OpenFile("notes.txt", os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer file.Close()

    _, err = file.WriteString("This line was appended.\n")
    if err != nil {
        fmt.Println("Error writing:", err)
        return
    }
    fmt.Println("Content appended")
}

Common File Open Flags

FlagMeaning
os.O_RDONLYOpen for reading only
os.O_WRONLYOpen for writing only
os.O_RDWROpen for reading and writing
os.O_CREATECreate file if it does not exist
os.O_APPENDAppend to end of file
os.O_TRUNCTruncate file to zero length on open

Checking If a File Exists

package main

import (
    "errors"
    "fmt"
    "os"
)

func fileExists(name string) bool {
    _, err := os.Stat(name)
    return !errors.Is(err, os.ErrNotExist)
}

func main() {
    fmt.Println(fileExists("notes.txt")) // true or false
}

Deleting a File

err := os.Remove("notes.txt")
if err != nil {
    fmt.Println("Could not delete file:", err)
}

Key Points

  • Use os.WriteFile and os.ReadFile for simple whole-file operations
  • Always call defer file.Close() immediately after opening a file
  • Use bufio.Scanner to read large files line by line without loading all into memory
  • Use os.OpenFile with flags like os.O_APPEND for more control over how the file opens
  • Check for os.ErrNotExist to determine if a file is missing before operating on it

Leave a Comment