Reading Files
package main
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
// Read all contents to the memory.
path := filepath.Join(os.TempDir(), "dat")
dat, err := os.ReadFile(path)
check(err)
fmt.Print(string(dat))
// When we want more control over how and what parts of a file are read.
// For these tasks, start by Opening a file to obtain an `os.File` value.
f, err := os.Open(path)
check(err)
defer f.Close() // Don't forget to close the file.
b1 := make([]byte, 5)
n1, err := f.Read(b1)
check(err)
fmt.Printf("%d bytes: %s\n", n1, string(b1[:n1]))
o2, err := f.Seek(6, io.SeekStart) // Count from start
check(err)
b2 := make([]byte, 2)
n2, err := f.Read(b2)
check(err)
fmt.Printf("%d bytes @ %d: ", n2, o2)
fmt.Printf("%s\n", string(b2[:n2]))
_, err = f.Seek(2, io.SeekCurrent) // Count from current position
check(err)
_, err = f.Seek(-4, io.SeekEnd) // Or count backwards from end
check(err)
o3, err := f.Seek(6, io.SeekStart)
check(err)
b3 := make([]byte, 2)
// A more robust choice, avoid short read.
n3, err := io.ReadAtLeast(f, b3, 2)
// Note if `len(b3) < 2`, panic will occurred.
// We can use `n3, err := io.ReadFull(f, b3)` instead.
check(err)
fmt.Printf("%d bytes @ %d: %s\n", n3, o3, string(b3))
_, err = f.Seek(0, io.SeekStart) // No rewind, use this.
check(err)
// The bufio package implements a buffered reader that may be useful
// both for its efficiency with many small reads and
// because of the additional reading methods it provides.
r4 := bufio.NewReader(f)
b4, err := r4.Peek(5) // Will not move the pointer.
check(err)
fmt.Printf("5 bytes: %s\n", string(b4))
}
/*
$ echo "hello" > /tmp/dat
$ echo "go" >> /tmp/dat
$ run cmd/read/read.go
hello
go
5 bytes: hello
2 bytes @ 6: go
2 bytes @ 6: go
5 bytes: hello
*/Writing Files
It follow the similar pattern to Reading Files
package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
// Dump bytes to the file directly.
d1 := []byte("hello\ngo\n")
path1 := filepath.Join(os.TempDir(), "dat1")
err := os.WriteFile(path1, d1, 0o644)
check(err)
// Create the file and get its pointer.
path2 := filepath.Join(os.TempDir(), "dat2")
f, err := os.Create(path2)
check(err)
defer f.Close()
// Write byte slices.
d2 := []byte{115, 111, 109, 101, 10}
n2, err := f.Write(d2)
check(err)
fmt.Printf("wrote %d bytes\n", n2)
// Write strings.
n3, err := f.WriteString("writes\n")
check(err)
fmt.Printf("wrote %d bytes\n", n3)
// Flush to storage
f.Sync()
// Buffered writers.
w := bufio.NewWriter(f)
n4, err := w.WriteString("buffered\n")
check(err)
fmt.Printf("wrote %d bytes\n", n4)
// Ensure buffers applied to writers
w.Flush()
}
/*
$ go run cmd/write/write.go
wrote 5 bytes
wrote 7 bytes
wrote 9 bytes
$ cat /tmp/dat1
hello
go
$ cat /tmp/dat2
some
writes
buffered
*/Logging
package main
import (
"bytes"
"fmt"
"log"
"log/slog"
"os"
)
func main() {
log.Println("Standard Log") // Output to `os.Stderr`
// Loggers can be configured with flags to set their output format.
// By default, the standard logger has the `log.Ldate` and `log.Ltime` flags set,
// and these are collected in `log.LstdFlags`.
// We can change its flags to emit time with microsecond accuracy, for example.
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.Println("with micro")
// Emitting the file name and line from which the log function is called.
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Println("with file/line")
// Create a logger with prefix specified.
mylog := log.New(os.Stdout, "my:", log.LstdFlags)
mylog.Println("from mylog")
// And change it.
mylog.SetPrefix("ohmy:")
mylog.Println("from mylog")
// The output target could be different from stdout.
var buf bytes.Buffer
buflog := log.New(&buf, "buf:", log.LstdFlags)
buflog.Println("hello")
fmt.Print("from buflog:", buf.String())
// The slog package provides structured log output.
// For example, logging in JSON format is straightforward.
jsonHandler := slog.NewJSONHandler(os.Stderr, nil)
myslog := slog.New(jsonHandler)
myslog.Info("hi there")
// In addition to the message, slog output can contain an arbitrary number of key=value pairs.
myslog.Info("hello again", "key", "val", "age", 25)
}
/*
2026/05/30 14:15:25 Standard Log
2026/05/30 14:15:25.508164 with micro
2026/05/30 14:15:25 logging.go:23: with file/line
my:2026/05/30 14:15:25 from mylog
ohmy:2026/05/30 14:15:25 from mylog
from buflog:buf:2026/05/30 14:15:25 hello
{"time":"2026-05-30T14:15:25.508210182+08:00","level":"INFO","msg":"hi there"}
{"time":"2026-05-30T14:15:25.508224497+08:00","level":"INFO","msg":"hello again","key":"val","age":25}
*/