Golang seek on files: os.File.Seek and whence constants

Golang file seek and go file seek with (*os.File).Seek: io.SeekStart, io.SeekCurrent, io.SeekEnd, temp-file examples, and bytes.Reader for in-memory golang seek.

Published

Updated

Read time 4 min read

Reviewed byDeepak Prasad

Golang seek on files: os.File.Seek and whence constants

This page is about golang file seek, golang seek file, go file seek, and plain golang seek: moving the I/O offset on an open file with (*os.File).Seek. Search phrases like seek file or seek go map to the same API. For opening paths and general file work, see how to work with files and read/update patterns. Unrelated queries (for example comma-ok idiom) are not covered here.

Examples use a temporary file on Linux with a recent Go toolchain. Check errors in production code; snippets keep error handling short for clarity.


Golang file seek: (*os.File).Seek and whence

Seek sets the offset for the next Read or Write on the file. The whence argument is usually one of:

  • io.SeekStart (0): offset is relative to the beginning of the file.
  • io.SeekCurrent (1): offset is added to the current position.
  • io.SeekEnd (2): offset is relative to the end (often negative to read a suffix).

The method returns the new absolute offset and an error. Seek on directories and on files opened with O_APPEND has platform-specific limits described in the os.File.Seek documentation.

Example: absolute seek then read (io.SeekStart)

This program writes known bytes to a temp file, reads the first word, then seeks to an absolute offset before the next read.

go
package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	payload := []byte("This is an example file. Hello GoLinuxCloud members!")
	f, err := os.CreateTemp("", "seek-demo-*.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	name := f.Name()
	defer os.Remove(name)
	if _, err := f.Write(payload); err != nil {
		fmt.Println(err)
		return
	}
	f.Close()

	file, err := os.Open(name)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer file.Close()

	buf := make([]byte, 4)
	n, err := file.Read(buf)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("first read (%d): %q\n", n, string(buf[:n]))

	if _, err := file.Seek(11, io.SeekStart); err != nil {
		fmt.Println(err)
		return
	}
	buf2 := make([]byte, 9)
	n2, err := file.Read(buf2)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("after SeekStart(11) read (%d): %q\n", n2, string(buf2[:n2]))
}

Run prints a four-byte prefix read, then nine bytes starting at offset 11 ("example f" in the sample payload).

Example: io.SeekCurrent, io.SeekEnd, and tail reads

go
package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	payload := []byte("This is an example file. Hello GoLinuxCloud members!")
	f, err := os.CreateTemp("", "seek-demo2-*.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	name := f.Name()
	defer os.Remove(name)
	if _, err := f.Write(payload); err != nil {
		fmt.Println(err)
		return
	}
	f.Close()

	file, err := os.Open(name)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer file.Close()

	buf := make([]byte, 4)
	if _, err := file.Read(buf); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("first 4 bytes: %q\n", string(buf))

	if _, err := file.Seek(4, io.SeekCurrent); err != nil {
		fmt.Println(err)
		return
	}
	buf2 := make([]byte, 7)
	n2, err := file.Read(buf2)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("after SeekCurrent(+4): %q\n", string(buf2[:n2]))

	if _, err := file.Seek(-5, io.SeekEnd); err != nil {
		fmt.Println(err)
		return
	}
	buf3 := make([]byte, 5)
	n3, err := file.Read(buf3)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("last 5 bytes: %q\n", string(buf3[:n3]))
}

Run shows This, then an exam from the relative seek, then the last five bytes of the payload (bers! in the fixed string).


Golang seek without a file: bytes.Reader

For parsers that want the same offset model on memory, bytes.Reader implements Seek:

go
package main

import (
	"bytes"
	"fmt"
	"io"
)

func main() {
	r := bytes.NewReader([]byte("hello"))
	if _, err := r.Seek(-2, io.SeekEnd); err != nil {
		fmt.Println(err)
		return
	}
	buf := make([]byte, 8)
	n, err := r.Read(buf)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%q\n", string(buf[:n]))
}
Output

Run prints "lo" from the end of the string.


Summary

Golang file seek and go file seek both come down to (*os.File).Seek with io.SeekStart, io.SeekCurrent, or io.SeekEnd, always paired with error handling and a clear next Read or Write. Golang seek file workflows should respect append mode and directory limitations from the docs. When you only need golang seek semantics on in-memory bytes, bytes.NewReader gives the same Seek model without touching the filesystem.


References


Frequently Asked Questions

1. Is the function called os.Seek?

No. Seek is a method on *os.File. Import io for io.SeekStart, io.SeekCurrent, and io.SeekEnd instead of hard-coded 0, 1, and 2.

2. What does Seek return?

It returns the new absolute offset and an error. Always check the error; not every stream is seekable.

3. Can I seek on strings in memory?

Use bytes.NewReader on a []byte or string cast to bytes; *bytes.Reader implements Seek the same way conceptually.

4. Why avoid O_APPEND with Seek?

The documentation states that Seek on a file opened with O_APPEND is not specified; do not rely on a stable offset for writes in that mode.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …