Video Coming Soon...
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:
-c-- counts bytes.-m-- counts chars.-w-- counts words.-l-- counts lines.
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
- bufio -- https://pkg.go.dev/bufio
- flag -- https://pkg.go.dev/flag
- fmt -- https://pkg.go.dev/fmt
- log -- https://pkg.go.dev/log
- os -- https://pkg.go.dev/os
New packages in this project:
- strings -- https://pkg.go.dev/strings
- utf8 -- https://pkg.go.dev/unicode/utf8
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 code
View Source file go-coreutils/wc/main.go Onlypackage 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:
- This is a good example for unit vs. integration testing.
- The test will only run the functions that main uses, but not main.
- This will do a unit test.
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.