ZetCode

Go file

last modified July 22, 2026

In this tutorial we explore file operations in Go. The article is organized into logical groups: querying file metadata (existence, size, modification time), CRUD operations (create, write, read, delete, remove directories, rename), file manipulation (copy, append, truncate), temporary files, directory listing and walking, and advanced operations such as file seeking.

The primary package for file operations is os. It provides functions for opening, creating, and deleting files, as well as retrieving file metadata via os.Stat. Helper packages include path/filepath for directory walking and bufio for buffered reading.

Since Go 1.16, many common file operations that previously required the ioutil package are available directly on os. For instance, ioutil.ReadFile and ioutil.WriteFile are now os.ReadFile and os.WriteFile.

Among the key functions, os.Stat returns the FileInfo structure describing a file, which provides its size, modification time, and permissions.

Go check if file exists

In the following example, we check if the given file exists.

main.go
package main

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

func main() {

    _, err := os.Stat("words.txt")

    if errors.Is(err, os.ErrNotExist) {

        fmt.Println("file does not exist")
    } else {

        fmt.Println("file exists")
    }
}

We call the os.Stat function on the file. If the function returns the os.ErrNotExist error, the file does not exist.

Go file size

In the following example, we get the file size.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    fInfo, err := os.Stat("words.txt")

    if err != nil {

        log.Fatal(err)
    }

    fsize := fInfo.Size()

    fmt.Printf("The file size is %d bytes\n", fsize)
}

First, we get the FileInfo structure with os.Stat. Then we get the size of the file in bytes from the structure with Size function.

Go file last modified time

In the following example, we get the last modification time of the given file.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    fileName := "words.txt"

    fileInfo, err := os.Stat(fileName)

    if err != nil {
        log.Fatal(err)
    }

    mTime := fileInfo.ModTime()

    fmt.Println(mTime)
}

We get the last modification time from the FileInfo structure using its ModTime function.

Go create file

The os.Create function creates or truncates the given file. If the file already exists, it is truncated. If the file does not exist, it is created with mode 0666.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    file, err := os.Create("empty.txt")

    if err != nil {
        log.Fatal(err)
    }

    defer file.Close()

    fmt.Println("file created")
}

The example creates an empty file.

Go write file

The os.WriteFile function writes data to the specified file. If the file does not exist, it is created; otherwise it is truncated before writing.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    fileName := "data.txt"

    val := "old\nfalcon\nsky\ncup\nforest\n"
    data := []byte(val)

    err := os.WriteFile(fileName, data, 0644)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("done")
}

The example writes a few words into a file.

val := "old\nfalcon\nsky\ncup\nforest\n"
data := []byte(val)

We have a string from which we create a slice of bytes.

err := os.WriteFile(fileName, data, 0644)

We write the slice of bytes to the given filename with the 0644 permissions. The permission value is an octal number: 6 grants read and write for the owner, 4 grants read-only for the group, and the last 4 grants read-only for others. The effective permissions may be further modified by the system's umask, which subtracts permissions from the requested value.

In the next example, we write a slice of strings to a file.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    fileName := "data.txt"

    f, err := os.Create(fileName)

    if err != nil {

        log.Fatal(err)
    }

    defer f.Close()

    words := []string{"sky", "falcon", "rock", "hawk"}

    for _, word := range words {

        _, err := f.WriteString(word)

        if err != nil {
            log.Fatal(err)
        }

        _, err = f.WriteString("\n")

        if err != nil {
            log.Fatal(err)
        }
    }

    fmt.Println("done")
}

The WriteString method writes a string to the file.

Go read file

The os.ReadFile function reads the file specified as a parameter and returns the contents. It reads the whole file into memory at once; therefore, it should not be used for very large files.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    content, err := os.ReadFile("words.txt")

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(content))
}

In the code example, we read the contents of a text file and print it to the console.

A more appropriate way for a large file is to read it line by line. This way the program does not take huge amounts of memory.

main.go
package main

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

func main() {

    f, err := os.Open("words.txt")

    if err != nil {
        log.Fatal(err)
    }

    defer f.Close()

    scanner := bufio.NewScanner(f)

    for scanner.Scan() {

        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

The example reads a text file line by line.

f, err := os.Open("words.txt")

The os.Open function opens the specified file for reading. If successful, the functions on the returned file can be used for reading; the associated file descriptor has mode O_RDONLY.

scanner := bufio.NewScanner(f)

The bufio.NewScanner function returns a new Scanner to read from.

for scanner.Scan() {

    fmt.Println(scanner.Text())
}

With the Scan function we advance to the next token. We get the advancement with the Text function. In the default mode, the Scan function advances by lines.

Go delete file

The os.Remove deletes the given file.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    err := os.Remove("words.txt")

    if err != nil {

        log.Fatal(err)
    }

    fmt.Println("file deleted")
}

The example removes a file.

Go remove directory

The os.RemoveAll function removes a file or an entire directory and all its contents. Unlike os.Remove, it works on non-empty directories.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    err := os.RemoveAll("mydir")

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("directory removed")
}

The example removes the mydir directory and everything inside it.

Go rename file

The os.Rename function renames (moves) a file or directory.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    err := os.Rename("words.txt", "words_backup.txt")

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("file renamed")
}

The example renames words.txt to words_backup.txt.

Go copy file

The io.Copy function copies from a source Reader to a destination Writer. It is more memory-efficient than reading the entire file into memory.

main.go
package main

import (
    "io"
    "log"
    "os"
)

func main() {

    src := "words.txt"
    dest := "words2.txt"

    srcFile, err := os.Open(src)

    if err != nil {
        log.Fatal(err)
    }
    defer srcFile.Close()

    destFile, err := os.Create(dest)

    if err != nil {
        log.Fatal(err)
    }
    defer destFile.Close()

    written, err := io.Copy(destFile, srcFile)

    if err != nil {
        log.Fatal(err)
    }

    log.Printf("copied %d bytes", written)
}

In the code example, we copy a file with io.Copy. It reads from the source file and writes to the destination file, returning the number of bytes copied.

Go append to file

In order to append to a file, we include the os.O_APPEND flag to the flags of the os.OpenFile function.

main.go
package main

import (
    "log"
    "os"
)

func main() {

    fileName := "words.txt"

    f, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)

    if err != nil {
        log.Fatal(err)
    }

    defer f.Close()

    if _, err := f.WriteString("cloud\n"); err != nil {

        log.Fatal(err)
    }
}

The example appends one word to the words.txt file using the WriteString function.

Go truncate file

The os.Truncate function changes the size of a file without modifying its permissions. It is useful for quickly emptying a file without recreating it.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    err := os.Truncate("words.txt", 0)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("file truncated")
}

The example truncates words.txt to zero length, effectively emptying it.

Go create temp file and directory

The os.CreateTemp and os.MkdirTemp functions create temporary files and directories with automatically generated names. They are commonly used in tests and data processing pipelines. The temporary items should be cleaned up with os.RemoveAll.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    tmpFile, err := os.CreateTemp("", "example-*.txt")

    if err != nil {
        log.Fatal(err)
    }

    defer os.Remove(tmpFile.Name())

    fmt.Println(tmpFile.Name())

    _, err = tmpFile.WriteString("temporary data")

    if err != nil {
        log.Fatal(err)
    }

    tmpDir, err := os.MkdirTemp("", "example-*")

    if err != nil {
        log.Fatal(err)
    }

    defer os.RemoveAll(tmpDir)

    fmt.Println(tmpDir)
}

The example creates a temp file and a temp directory. The * in the pattern is replaced with a random string. Both are cleaned up with defer.

Go list directory

The os.ReadDir function (Go 1.16+) reads the contents of a directory and returns a slice of os.DirEntry values.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    entries, err := os.ReadDir(".")

    if err != nil {
        log.Fatal(err)
    }

    for _, entry := range entries {

        fmt.Println(entry.Name())
    }
}

The example lists all files and directories in the current working directory.

Go list files

The filepath.Walk walks the file tree, calling the specified function for each file or directory in the tree, including root. The function is recursively walking all subdirectories.

main.go
package main

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

func main() {

    var files []string

    root := "."

    err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {

        if err != nil {

            fmt.Println(err)
            return nil
        }

        if !info.IsDir() && filepath.Ext(path) == ".txt" {
            files = append(files, path)
        }

        return nil
    })

    if err != nil {
        log.Fatal(err)
    }

    for _, file := range files {
        fmt.Println(file)
    }
}

In the code example, we search for files with .txt extension.

var files []string

The matching files are stored in the files slice.

root := "."

This is the root directory where we start searching.

err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {

The first parameter of the filepath.Walk is the root directory. The second parameter is the walk function; the function called by filepath.Walk to visit each each file or directory.

if err != nil {

    fmt.Println(err)
    return nil
}

Print the error if there is one, but continue searching elsewhere.

if !info.IsDir() && filepath.Ext(path) == ".txt" {
    files = append(files, path)
}

We append the file to the files slice if the file is not a directory and it has the .txt extension.

for _, file := range files {
    fmt.Println(file)
}

Finally, we go over the files slice and print all matching files to the console.

Go walk directory with WalkDir

Starting from Go 1.16, filepath.WalkDir is available as a more efficient alternative to filepath.Walk. Instead of os.FileInfo, the callback receives os.DirEntry which avoids an extra os.Stat call on every visited item.

main.go
package main

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

func main() {

    var files []string

    root := "."

    err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {

        if err != nil {
            fmt.Println(err)
            return nil
        }

        if !d.IsDir() && filepath.Ext(path) == ".txt" {
            files = append(files, path)
        }

        return nil
    })

    if err != nil {
        log.Fatal(err)
    }

    for _, file := range files {
        fmt.Println(file)
    }
}

The example is a rewrite of the previous filepath.Walk example using WalkDir. The callback uses os.DirEntry instead of os.FileInfo, and the root directory is set to the current working directory (.).

Go file seek

The Seek method sets the offset for the next read or write on a file. It returns the new offset relative to the start of the file. This enables random access — reading or overwriting data at a specific position.

main.go
package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    f, err := os.OpenFile("words.txt", os.O_RDWR, 0644)

    if err != nil {
        log.Fatal(err)
    }

    defer f.Close()

    // seek to the 6th byte
    _, err = f.Seek(6, 0)

    if err != nil {
        log.Fatal(err)
    }

    buf := make([]byte, 4)
    n, err := f.Read(buf)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("read %d bytes: %s\n", n, string(buf))
}

The example opens a file in read-write mode, seeks to byte offset 6, and reads four bytes from that position.

Source

Go os package - reference

In this article we have worked with files in Golang.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Go tutorials.