Video Coming Soon...

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

34: tr

The tr command is another very useful tool that "translates" one set of characters to another set. It reads the input, looks at each character, and changes any that match the input set to the output set.

Most of the documentation for tr talks about "array1 and array1" which are just the two sets of characters you give tr as command line options.

The Challenge

You are required to implement the following options to tr:

You can find the documentation for tr here:

Requirements

I used all of the basic packages you'll need for most of these tools like bufio and os, but also strings this time.

See the list of requirements

Spoilers

As usual, my implementation is only a few of the options and just enough to get you started if you're stuck.

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

package main

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

type Opts struct {
  Compliment bool
  Delete bool
  Squeeze bool
  Truncate bool
}

type Translator struct {
  opts Opts
  TwoArgs bool
  RuneMap map[rune]rune
}

func (tr *Translator) ParseOpts() {
  var from []rune
  var to []rune
  tr.RuneMap = make(map[rune]rune)
  tr.TwoArgs = true

  flag.BoolVar(&tr.opts.Compliment, "c", false, "Use compliment of array1")
  flag.BoolVar(&tr.opts.Delete, "d", false, "Delete chars in array1")
  flag.BoolVar(&tr.opts.Squeeze, "s", false, "Squeeze chars in array2")
  flag.BoolVar(&tr.opts.Truncate, "t", false, "Truncate array1 to array2 length")
  flag.Parse()

  args := flag.Args()

  if flag.NArg() == 1 {
    from, to = []rune(args[0]), []rune(args[0])
    tr.TwoArgs = false
  } else if flag.NArg() == 2 {
    from, to = []rune(args[0]), []rune(args[1])
  } else {
    log.Fatal("USAGE: tr [-c] [-d] [-s] [-t] <array1> [array2]")
  }

  for i, ch := range from {
    tr.RuneMap[ch] = to[i]
  }
}

func (tr *Translator) Translate(from rune) rune {
  if !tr.TwoArgs { log.Fatal("translate requires two args") }

  to, ok := tr.RuneMap[from]

  if ok {
    return to
  } else {
    return from
  }
}

func (tr *Translator) Delete(line string) string {
  result := make([]rune, 0, len(line))

  for _, ch := range []rune(line) {
    _, ok := tr.RuneMap[ch]

    if !ok {
      result = append(result, ch)
    }
  }

  return string(result)
}

func (tr *Translator) Process(line string) {
  if tr.opts.Delete {
    line = tr.Delete(line)
  } else {
    line = strings.Map(tr.Translate, line)
  }

  fmt.Print(line)
}

func main() {
  tr := new(Translator)
  tr.ParseOpts()

  scan := bufio.NewScanner(os.Stdin)

  for scan.Scan() {
    line := scan.Text() + "\n"

    tr.Process(line)
  }
}

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.