Skip to main content

File Handling in Go

Hey there! In this guide, we'll explore File Handling in Go. Reading and writing files is a common task in software development, and Go's os and io packages make this incredibly straightforward. Let's dive in!

1. Reading an Entire File

If you want to read the entire contents of a file into memory at once, you can use the os.ReadFile function.

package main

import (
"fmt"
"os"
)

func main() {
// ReadFile returns a byte slice ([]byte) and an error
data, err := os.ReadFile("hello.txt")
if err != nil {
fmt.Println("Error reading file:", err)
return
}

// Convert the byte slice to a string to print it
fmt.Println(string(data))
}

2. Writing to a File

Similarly, to write a byte slice to a file quickly, use os.WriteFile. If the file does not exist, it will be created. If it does exist, it will be overwritten.

package main

import (
"fmt"
"os"
)

func main() {
message := []byte("Hello, Gophers!")

// 0644 are standard Unix file permissions (Read/Write for owner, Read for others)
err := os.WriteFile("output.txt", message, 0644)
if err != nil {
fmt.Println("Error writing to file:", err)
return
}
fmt.Println("File written successfully!")
}

3. Reading a File Line by Line

If a file is very large, reading it all into memory at once might crash your program. Instead, you can open the file and read it line by line using a Scanner from the bufio package.

package main

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

func main() {
// 1. Open the file
file, err := os.Open("data.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}

// 2. Ensure the file is closed when the function exits
defer file.Close()

// 3. Create a scanner
scanner := bufio.NewScanner(file)

// 4. Read line by line
for scanner.Scan() {
fmt.Println(scanner.Text()) // Print the current line
}

// 5. Check for any errors that occurred during scanning
if err := scanner.Err(); err != nil {
fmt.Println("Error scanning file:", err)
}
}

The defer Keyword

Notice the use of defer file.Close(). defer schedules a function call to be run immediately before the surrounding function (main in this case) returns. This is the idiomatic way in Go to ensure resources like files and network connections are always properly closed, even if an error occurs later in the function!

4. Appending to a File

If you want to add text to the end of an existing file rather than overwriting it, you need to open it with specific flags using os.OpenFile.

func main() {
// Open the file with Append and Create flags
file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error:", err)
return
}
defer file.Close()

// Write a string to the file
if _, err := file.WriteString("New log entry!\n"); err != nil {
fmt.Println("Error writing:", err)
}
}

5. Best Practices

  • Always close files: Resource leaks are a common source of bugs. Get into the habit of typing defer file.Close() immediately after checking the error from os.Open.
  • Use os.ReadFile for small files only: It's convenient, but loads everything into RAM. For large logs or datasets, always stream the file using bufio.Scanner or io.Reader.