Working with the File System in Go: A Practical Guide
Go (Golang) is a modern compiled language that is excellent for building server applications, utilities, and command-line tools. One of the key tasks in such projects is working with the file system. In this article, we will cover practical examples: from opening and reading files to recursively traversing directories. All examples are written in pure Go without third-party libraries.
Basics: Opening, Reading, and Writing Files
The os package provides functions for low-level file operations. To read the entire contents of a file into memory, use os.ReadFile (available since Go 1.16).
package main
import ( "fmt" "os")
func main() { data, err := os.ReadFile("example.txt") if err != nil { fmt.Println("Error reading file:", err) return } fmt.Println(string(data))}To write data to a file, use the os.WriteFile function. It creates the file if it does not exist, or overwrites an existing one.
package main
import ( "fmt" "os")
func main() { content := []byte("Hello, Go!Second line") err := os.WriteFile("output.txt", content, 0644) if err != nil { fmt.Println("Error writing:", err) return } fmt.Println("File successfully written")}The third argument 0644 is the Unix-style permission (owner can read and write, others can only read). On Windows, this parameter is partially ignored.
Streaming Read and Write: Working with Large Files
For working with large files (more than a few megabytes), you should not load them entirely into memory. Use buffered reading via bufio or the io package.
package main
import ( "bufio" "fmt" "os")
func main() { file, err := os.Open("large_file.log") if err != nil { fmt.Println("Error opening:", err) return } defer file.Close()
scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() // Process the line fmt.Println(line) }
if err := scanner.Err(); err != nil { fmt.Println("Scanning error:", err) }}For streaming writes, use bufio.NewWriter:
package main
import ( "bufio" "fmt" "os")
func main() { file, err := os.Create("output_stream.txt") if err != nil { fmt.Println("Error creating:", err) return } defer file.Close()
writer := bufio.NewWriter(file) for i := 0; i < 10; i++ { _, err := writer.WriteString(fmt.Sprintf("Line %d", i+1)) if err != nil { fmt.Println("Error writing:", err) return } } writer.Flush() // Important: flush the buffer fmt.Println("Writing completed")}The Flush() method is mandatory — without it, data may remain in the buffer and not be written to the file.
Working with Directories: Creating, Deleting, and Traversing
You can create a new directory using os.Mkdir (single folder) or os.MkdirAll (recursively, like mkdir -p).
package main
import ( "fmt" "os")
func main() { // Create a single folder err := os.Mkdir("new_folder", 0755) if err != nil { fmt.Println("Error creating folder:", err) }
// Create a nested structure err = os.MkdirAll("a/b/c/d", 0755) if err != nil { fmt.Println("Error creating nested folders:", err) } else { fmt.Println("Folders created") }}Deleting a directory is done via os.Remove (only if empty) or os.RemoveAll (recursively).