Video Coming Soon...

Created by Zed A. Shaw Updated 2025-11-14 00:08:54

33: wc

The wc command is so useful I even use it in Windows...by running wsl wc. I know, kind of dumb but that's how handy it is. The wc command does "word count" but it can count lines, words, characters, and bytes in a file. Mostly I use it to count the number of lines.

The Challenge

You'll be required to implement the following options to wc:

That's actually most of the tools functionality, so let's add on that your output has to exactly match the output of the original tool.

You can find the documentation here:

Requirements

I found that I used mostly the same things from the od command, and you'll probably use those packages all the time in these projects. I also used the following packages:

See the list of requirements

New packages in this project:

Spoilers

As usual, try to do this without looking at my code, but if you get stuck there's no shame in taking a peak or full on doing a copy of my work:

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

package main

import (
  "fmt"
  "os"
  "log"
  "flag"
  "bufio"
  "strings"
  "unicode/utf8"
)

type Opts struct {
  Bytes bool
  Chars bool
  Words bool
  Lines bool
}

type Counts struct {
  Bytes int
  Chars int
  Words int
  Lines int
  Filename string
}


func ParseOpts() (Opts, []string) {
  var opts Opts

  flag.BoolVar(&opts.Bytes, "c", false, "Count bytes")
  flag.BoolVar(&opts.Chars, "m", false, "Count chars")
  flag.BoolVar(&opts.Words, "w", false, "Count words")
  flag.BoolVar(&opts.Lines, "l", false, "Count lines")

  flag.Parse()

  if flag.NArg() == 0 {
    log.Fatal("USAGE: wc [-l] [-w] [-m] [-c] <files>")
  }

  if !opts.Bytes && !opts.Chars && !opts.Words && !opts.Lines {
    opts.Lines = true
  }

  return opts, flag.Args()
}


func CountFile(opts *Opts, filename string) Counts {
  var counts Counts

  in_file, err := os.Open(filename)
  if err != nil { log.Fatal(err) }
  defer in_file.Close()

  scan := bufio.NewScanner(in_file)
  for scan.Scan() {
    line := scan.Text()

    if opts.Lines {
      counts.Lines++
    }

    if opts.Words {
      counts.Words += len(strings.Fields(line))
    }

    if opts.Chars {
      counts.Chars += utf8.RuneCountInString(line) + 1
    }

    if opts.Bytes {
      counts.Bytes += len(line) + 1
    }
  }

  if scan.Err() != nil {
    log.Fatal(scan.Err())
  }

  return counts
}

func print_count(opts *Opts, count *Counts, file string) {
  if opts.Lines {
    fmt.Print(count.Lines, " ")
  }

  if opts.Words {
    fmt.Print(count.Words, " ")
  }

  if opts.Chars {
    fmt.Print(count.Chars, " ")
  }

  if opts.Bytes {
    fmt.Print(count.Bytes, " ")
  }

  fmt.Println(" ", file)
}

func main() {
  opts, files := ParseOpts()

  for _, file := range files {
    count := CountFile(&opts, file)
    print_count(&opts, &count, file)
  }
}

Remember that the point of these exercises is not to test your skill, but to build your ability to create things on your own. Everyone copies other people's work. It's how we learn even as babies. You are only attempting these solo at first so you get better at the processes needed to create your own ideas.

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.

Notes:

  1. This is a good example for unit vs. integration testing.
  2. The test will only run the functions that main uses, but not main.
  3. This will do a unit test.
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.