File Handling in Go
By Syncfusion®, Inc. · Last updated
Create, read, write and append files with os and bufio, make and list directories, read file info and permissions, and handle missing-file and permission errors.
What Is File Handling?
File = data saved on a disk with a name, like marks.csv or notes.txt.
File handling = creating, reading, writing and deleting files from your program.
Simple lesson: file handling lets a program save data and read it back later.
A Real-World Example
Think of a class notebook kept in the staff room.
- Without it, the teacher would forget the marks when the bell rings.
- She writes the marks in the notebook.
- Next week, she reads them back.
- At the end of term, she adds new pages.
Now the mapping:
- The notebook = a file.
- The staff room shelf = a folder (directory).
- Writing, reading, adding pages =
WriteFile,ReadFile, appending.
Why File Handling Is Important
- Variables vanish when the program ends. Files stay.
- Settings are kept in files, like
config.json. - Data is shared between programs through files: reports, logs, CSV sheets.
- Logs in files help you find out what went wrong later.
In this lab, every program gets the same pretend disk in its folder:
notes.txt,marks.csv, and a read-onlylocked.txt- a
datafolder withstudents.jsonandconfig.json
Each run starts fresh. On your computer, the same code reads and writes your real disk.
What You Will Learn
In this lesson, you will learn:
- How to create, read, write and append files.
- The quick helpers
os.ReadFile()andos.WriteFile(). - How to create and read directories.
- How to read file information and permissions.
- How to handle missing files and permission errors.
File Operations
The Quick Way: os.ReadFile() and os.WriteFile()
Read a whole file
package main
import (
"fmt"
"os"
)
func main() {
data, err := os.ReadFile("notes.txt")
if err != nil {
fmt.Println("could not read:", err)
return
}
fmt.Print(string(data))
fmt.Println("bytes:", len(data))
}
Bring your geometry box.
Maths test on Friday.
bytes: 47
os.ReadFilereads the whole file into a[]byte.string(data)turns the bytes into text.- Always check
err. See Error Handling.
Write a file
package main
import (
"fmt"
"os"
)
func main() {
text := "Annual day: 20 December\n"
err := os.WriteFile("notice.txt", []byte(text), 0644)
fmt.Println(err)
data, _ := os.ReadFile("notice.txt")
fmt.Print(string(data))
}
<nil>
Annual day: 20 December
os.WriteFilecreates the file, or replaces everything in it.0644is the permission: you can read and write, others can only read. See Permissions.
Try every action on the pretend disk.
Compare these:
- WriteFile twice replaces the old text.
- Append adds a line at the end.
- ReadFile on a missing name gives
no such file or directory. - WriteFile on
locked.txtgivespermission denied.
Create Files
os.Create = make a new empty file (or empty an old one), and open it for writing.
Create and write step by step
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Create("attendance.txt")
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
fmt.Fprintln(f, "Roll 1: present")
fmt.Fprintln(f, "Roll 2: absent")
f.WriteString("Roll 3: present\n")
data, _ := os.ReadFile("attendance.txt")
fmt.Print(string(data))
}
Roll 1: present
Roll 2: absent
Roll 3: present
os.Creategives an*os.File.fmt.Fprintln(f, ...)writes a line into the file, likePrintlnwrites to the screen.defer f.Close()makes sure the file is closed whenmainends. See Functions.
Read Files Line by Line
For big files, read one line at a time with bufio.Scanner.
Read marks.csv line by line
bufio.Scanneruses little memory, even for a huge file.- Use
strings.Split(sc.Text(), ",")to cut a CSV line into fields. See Strings.
Append Files
Append = add to the end of a file, keeping what is already there.
Add a line to a log
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.OpenFile("notes.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
fmt.Println(err)
return
}
fmt.Fprintln(f, "Science project due Monday.")
f.Close()
data, _ := os.ReadFile("notes.txt")
fmt.Print(string(data))
}
Bring your geometry box.
Maths test on Friday.
Science project due Monday.
| Flag | Means |
|---|---|
os.O_APPEND | write at the end |
os.O_WRONLY | open for writing only |
os.O_CREATE | create the file if it is missing |
os.O_TRUNC | empty the file first |
- Join flags with
|. See Operators.
Which function to use
Simple lesson: use ReadFile and WriteFile for small files, a Scanner for line-by-line reading, and O_APPEND to add to the end.
Directory Operations
Directory = a folder that holds files and other folders.
Create Directories
Make folders
package main
import (
"fmt"
"os"
)
func main() {
err := os.Mkdir("reports", 0755)
fmt.Println(err)
err = os.MkdirAll("reports/2025/term1", 0755)
fmt.Println(err)
err = os.Mkdir("reports", 0755)
fmt.Println(err)
}
<nil>
<nil>
mkdir reports: file exists
os.Mkdirmakes one folder. It fails if the folder exists.os.MkdirAllmakes every missing folder on the path, and does not fail if they exist.
Read Directories
List a folder
package main
import (
"fmt"
"os"
)
func main() {
entries, err := os.ReadDir(".")
if err != nil {
fmt.Println(err)
return
}
for _, e := range entries {
kind := "file"
if e.IsDir() {
kind = "folder"
}
fmt.Println(kind, e.Name())
}
}
folder data
file locked.txt
file marks.csv
file notes.txt
"."means "the current folder".os.ReadDirgives the entries sorted by name.
Deleting
os.Remove(name)deletes one file, or one empty folder.os.RemoveAll(path)deletes a folder and everything inside. Use it with great care.os.Rename(old, new)renames or moves a file.
Simple lesson: MkdirAll makes folders, ReadDir lists them, and Remove deletes.
File Metadata
Metadata = data about a file: its name, size, type, permissions and when it changed.
File Info
os.Stat
package main
import (
"fmt"
"os"
)
func main() {
info, err := os.Stat("marks.csv")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Name:", info.Name())
fmt.Println("Size:", info.Size(), "bytes")
fmt.Println("Folder?", info.IsDir())
fmt.Println("Mode:", info.Mode())
}
Name: marks.csv
Size: 53 bytes
Folder? false
Mode: -rw-r--r--
info.ModTime()gives the last time the file changed.
Permissions
Permission = who may read, write or run a file.
- Written as three digits, in octal:
0644,0755. - The digits are for: owner, group, everyone else.
- Each digit adds: read = 4, write = 2, run (execute) = 1.
| Number | Letters | Meaning |
|---|---|---|
0644 | -rw-r--r-- | owner reads and writes, others read |
0755 | drwxr-xr-x (folder) | owner does everything, others read and enter |
0444 | -r--r--r-- | everyone can only read |
- On Windows, Go keeps only "read-only or not". The numbers matter most on Linux and macOS.
Simple lesson: os.Stat tells you a file's size, type and permissions.
File Error Handling
Missing Files
Check for a missing file
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main() {
_, err := os.ReadFile("homework.txt")
fmt.Println(err)
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("No homework file yet. Creating one.")
os.WriteFile("homework.txt", []byte("none\n"), 0644)
}
data, _ := os.ReadFile("homework.txt")
fmt.Print(string(data))
}
open homework.txt: no such file or directory
No homework file yet. Creating one.
none
errors.Is(err, fs.ErrNotExist)checks for "file does not exist", even when wrapped.os.IsNotExist(err)is an older way to ask the same question.
Permission Errors
A read-only file
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main() {
err := os.WriteFile("locked.txt", []byte("changed"), 0644)
fmt.Println(err)
fmt.Println(errors.Is(err, fs.ErrPermission))
data, _ := os.ReadFile("locked.txt")
fmt.Print(string(data))
}
open locked.txt: permission denied
true
Only the office can change this.
- Reading still works. Writing is refused.
- The file is not changed.
Always close files you open: defer f.Close() right after a successful Open or Create.
An open file uses system resources, and written data may not be saved until the file is closed.
Easy way to remember
- Small file:
os.ReadFile,os.WriteFile - Line by line:
os.Open+bufio.Scanner - Add to the end:
os.OpenFilewithO_APPEND - Missing:
errors.Is(err, fs.ErrNotExist)
Simple lesson: check every file error, and use errors.Is to tell "missing" from "not allowed".
TL;DR
- File handling = save and load data that outlives the program.
- Read all =
os.ReadFile(name). Write all =os.WriteFile(name, data, 0644). - Create =
os.Create(name), thenfmt.Fprintln(f, ...),defer f.Close(). - Line by line =
os.Open+bufio.NewScanner+sc.Scan()/sc.Text(). - Append =
os.OpenFile(name, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644). - Folders =
os.Mkdir,os.MkdirAll,os.ReadDir,os.Remove,os.RemoveAll. - Metadata =
os.Stat→Name(),Size(),IsDir(),Mode(). - Errors =
fs.ErrNotExist,fs.ErrPermission, checked witherrors.Is.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
Ignoring the error from Open | The file is always there | Check err first | A nil-file panic later. |
Forgetting f.Close() | Go closes files for you | Close with defer f.Close() | Leaked files, lost writes. |
os.WriteFile to add a line | It appends | It replaces the whole file | Old content is lost. |
os.Mkdir("a/b/c", 0755) | It makes every folder | Use MkdirAll | no such file or directory |
Permission 644 without 0 | Same as 0644 | Without 0, it is decimal 644 | Strange permissions. |
err == os.ErrNotExist | Errors compare with == | Use errors.Is | False for wrapped errors. |
string(data) on a huge file | It is fine for any size | Read big files line by line | Slow, high memory use. |
Check Yourself
Exercises
What to Remember
- Files keep data after the program ends.
os.ReadFileandos.WriteFilehandle small files in one call.- Use
os.Open+bufio.Scannerfor line-by-line reading. O_APPENDadds to the end.WriteFilereplaces everything.MkdirAll,ReadDirandStatwork with folders and file details.- Close files with
defer f.Close(), and check errors witherrors.Is.
Next: JSON. You will learn how to turn Go structs into JSON text, and read JSON back into structs.