Video Coming Soon...

Created by Zed A. Shaw Updated 2025-11-07 07:03:42

32: od

I believe I'd never used od until I wrote this exercise. That's one of the great things about these projects. You'll learn about tools you may never consider while you're learning about the Go standard library.

The od tool is a way to view the contents of a binary file as a sequence of octal values. Think of it as "cat for hackers." It's not the greatest way to view a binary file, and most people can't even read its output, but it is fun to implement.

The Challenge

You'll be required to implement the arguments -w, -x, and -o for the command line tool od. The instructions for od are:

You should try to use each option to understand what they do.

Requirements

While working on od I used the following libraries. You don't have to use these, but if you aren't sure where to start, these libraries are most likely a good place:

See the list of requirements

Spoilers

If you are totally stuck and can't figure it out, then you can view my code. If you're really, really stuck then take the time to do a copy of my code.

See my first version codeView Source file go-coreutils/od/main.go Only

package main

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

func Fail(err error, format string, v ...any) {
  err_format := fmt.Sprintf("ERROR: %v; %s", err, format)
  log.Printf(err_format, v...)
  os.Exit(1)
}

type Opts struct {
  Width int
  Filenames []string
  Hex bool
  Octal bool
  Format string
}

func parse_opts() (Opts) {
  var opts Opts

  flag.IntVar(&opts.Width, "w", 16, "Width of output grid")
  flag.BoolVar(&opts.Hex, "x", false, "Output hex bytes")
  flag.BoolVar(&opts.Octal, "o", false, "Output octal bytes")

  flag.Parse()

  if flag.NArg() == 0 {
    log.Fatal("USAGE: od [files]")
  }

  if opts.Hex {
    opts.Format = "%0.2x "
  } else {
    opts.Format = "%0.3o "
  }

  opts.Filenames = flag.Args()
  return opts
}

func main() {
  opts := parse_opts()

  for _, filename := range opts.Filenames {
    reader, err := os.Open(filename)
    defer reader.Close()
    if err != nil { Fail(err, "can't open: %s", filename) }

    buf := bufio.NewReader(reader)
    count := buf.Size()

    fmt.Printf("%0.8o ", 0);

    for index := 0; index < count; index++ {
      data, err := buf.ReadByte()
      if err != nil { break }
      fmt.Printf(opts.Format, data);

      if (index + 1) % opts.Width == 0 {
        fmt.Print("\n")
        fmt.Printf("%0.8o ", index);
      }
    }
  }
}

If you do a copy, then follow the Remake It process. Get your copy working, take notes on how it worked, then delete it and do a version based on your notes.

Testing It

If you want to push your learning further then you can try to implement an automated test for this. I actually need to learn how to test utilities like this with Go, so for now just consider this an extra challenge for later until I learn how to teach it.

Previous Lesson Next Lesson

Register for Learn Go the Hard Way

Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.