In this chapter, I'm sharing the string manipulation cheatsheet in part I. to keep on hand for reference when working with strings, which you will be doing a lot of when it comes to automating day-to-day work. Then, you'll find a few useful projects to help flex your string manipulation learnings in part II.

Note: Here's the GitHub for the Project Section after the Cheat Sheet

I. String Manipulation cheatsheet

Print this out, tape it by your desk, or put it in your favorite three-ring binder for school. It contains almost every string manipulation with an expression as an example for when you're working.

String Literals

Expression Result Comment
" " Empty string is default zero value for the string type
Japan 日本 Japan 日本 Go has UTF-8 built in
\\ \ Backslash
\n newline
\t tab

Concatenate

Expression Result Comment
"Go"+"Lang" Golang concatenation

Equal and Compare

Expression Result Comment
"Go" == "Go" true Equality
strings.EqualFold("GoLang") GOLANG true unicode case folding
"Go" < "go" true Lexicographic order

Length in bytes or runes

Expression Result Comment
len("行く") 6 Length in Bytes
utf8.RuneCountInString("行く") 2 in runes
utf8.ValidString("行く") true UTF-8

Index, substring, iterate

Expression Result Comment
"GO"[1] O byte position at 1
"GoLang"[1:3] oL byte indexing
"GO"[:2] GO byte indexing
"GO"[1:] O byte indexing
Expression Result Comment
strings.Contains("GoLang", "abc") false Is "abc" in GoLang
strings.Contains("GoLang","abc") true Is 'a' or 'b' or 'c' in Golang
strings.Count("Orange","an") 1 non-overlapping instances of range
strings.HasPrefix("GoLang", "Go") true Does GoLang start with 'Go'
strings.HasSuffix("GoLang", "Lang") true Does GoLang end with 'Lang'
strings.Index("GoLang", "abc") -3 Index of first abc
strings.IndexAny("GoLang", "abc") 3 a or b or c
strings.LastIndex("GoLang", "abc") -3 Index of last abc
strings.LastIndexAny("GoLang", "abc") 3 a or b or c

Replace

Expression Result Comment
strings.Replace("foo", "o", ".",2) f.. Replace first two “o” with “.”
strings.ToUpper("golang") GOLANG Uppercase
strings.ToLower("GoLang") golang Lowercase
strings.Title("go lang") Go Lang Initial letters to uppercase
strings.TrimSpace(" foo\n") foo Strip leading and trailing white space
strings.Trim("foo", "fo") Strip leading and trailing f:s and o:s
strings.TrimLeft("foo", "f") oo only leading
strings.TrimRight("foo", "o") f only trailing
strings.TrimPrefix("foo", "fo") o
strings.TrimSuffix("foo", "o")

Split by space or comma

Expression Result Comment
strings.Fields(" a\t b\n") ["a" "b"] Remove white space
strings.Split("a,b", ",") ["a" "b"] Remove separator
strings.SplitAfter("a,b",",") ["a," "b"] Keep separator

Join strings with a Separator

Expression Result Comment
strings.Join([]string{"a","b"}, ":") a:b Add ':' as separator
strings.Repeat("da", 2) dada 2 copies of “da”

Format and convert

Expression, Result, Comment
strconv.Itoa(-42), -42 Int to string
strconv.FormatInt(255, 16), ff Base 16

Regular Expressions

Expression, Meaning
., any character
[ab], the character a or b
[^ab], any character except a or b
[a-z], any character from a to z
[a-z0-9], any character from a to z or 0 to 9
\d, a digit: [0-9]
\D, a non-digit: [^0-9]
\s, a whitespace character: [\t\n\f\r ]
\S, a non-whitespace character: [^\t\n\f\r ]
\w, a word character: [0-9A-Za-z_]
\W, a non-word character: [^0-9A-Za-z_]
\p{Greek}, Unicode character class*
\pN, one-letter name
\P{Greek}, negated Unicode character class*
\PN, one-letter name
Regexp, Meaning
x*, zero or more x prefer more
x*?, prefer fewer (non-greedy)
x+, one or more x prefer more
x+?, prefer fewer (non-greedy)
x?, zero or one x prefer one
x??, prefer zero
x{n}, exactly n x
Regexp, Meaning
xy, x followed by y
x|y, x or y prefer x
xy|z, same as (xy)|z
xy*, same as x(y*)
Symbol, Matches
\A, at beginning of text
^, at beginning of text or line
$, at end of text
\z,
\b, at ASCII word boundary
\B, not at ASCII word boundary

First Match

re := regexp.MustCompile(`foo.?`)
fmt.Printf("%q\n", re.FindString("seafood fool")) // "food"
fmt.Printf("%q\n", re.FindString("meat"))         // ""

Location

re := regexp.MustCompile(`ab?`)
fmt.Println(re.FindStringIndex("tablett"))    // [1 3]
fmt.Println(re.FindStringIndex("foo") == nil) // true

All Matches

re := regexp.MustCompile(`a.`)
fmt.Printf("%q\n", re.FindAllString("paranormal", -1)) // ["ar" "an" "al"]
fmt.Printf("%q\n", re.FindAllString("paranormal", 2))  // ["ar" "an"]
fmt.Printf("%q\n", re.FindAllString("graal", -1))      // ["aa"]
fmt.Printf("%q\n", re.FindAllString("none", -1))       // [] (nil slice)

Replace

re := regexp.MustCompile(`ab*`)
fmt.Printf("%q\n", re.ReplaceAllString("-a-abb-", "T")) // "-T-T-"

Split

a := regexp.MustCompile(`a`)
fmt.Printf("%q\n", a.Split("banana", -1)) // ["b" "n" "n" ""]
fmt.Printf("%q\n", a.Split("banana", 0))  // [] (nil slice)
fmt.Printf("%q\n", a.Split("banana", 1))  // ["banana"]
fmt.Printf("%q\n", a.Split("banana", 2))  // ["b" "nana"]

zp := regexp.MustCompile(`z+`)
fmt.Printf("%q\n", zp.Split("pizza", -1)) // ["pi" "a"]
fmt.Printf("%q\n", zp.Split("pizza", 0))  // [] (nil slice)
fmt.Printf("%q\n", zp.Split("pizza", 1))  // ["pizza"]
fmt.Printf("%q\n", zp.Split("pizza", 2))  // ["pi" "a"]

II. Projects

Regex E-Mail finder from clipboard

In this project, we'll use Regex to determine if a piece of text copied to our clipboard contains an e-mail. The pattern of finding an e-mail in a string of text is handy as you'll probably receive data where it will be useful to the group based on a common characteristic.

If you're interested in building a web application with sign-up / sign-in input fields based on e-mails, this can be useful for determining whether the input is of the correct form.

Note: There is a third-party package dependency in this project, but I trust you remember how to handle that requirement hint go mod init & go get

package main

import (
	"fmt"
	"regexp"
	"strings"
	"github.com/atotto/clipboard"
)

func main() {
	// create email regexp
	regMail, _ := regexp.Compile(`[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}`)

	// read os buffer
	// find email regexp
	text, _ := clipboard.ReadAll()
	var mailAddr []string
	// found e-mail
	if regMail.MatchString(text) {
		mailAddr = regMail.FindAllString(text, -1)
	}

	// Print found e-mails on the terminal
	if len(mailAddr) > 0 {
		clipboard.WriteAll(strings.Join(mailAddr, "\n"))
		fmt.Println("Copied to clipboard:")
		fmt.Println(strings.Join(mailAddr, "\n"))
	} else {
		fmt.Println("No email addresses found.")
	}
}

Regex Password Complexity Checker

Password complexity is an additional feature you will want to have for an application. This is because simple passwords are easier for cyber criminals to obtain/guess.

Hence, it is wise to enforce a password complexity of at least 11 characters with some capital letters and numbers thrown in.  So, let's build a small program to check whether or not a given password passes the complexity requirement.

package main

import (
	"fmt"
	"os"
	"regexp"

	"flag"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Printf("Usage: %s -h\n", os.Args[0])
	} else {
		pass := flag.String("p", "", "get password")

		flag.Parse()

		regStr, _ := regexp.Compile(`([0-9a-zA-Z]){11,}`)

		if regStr.MatchString(*pass) {
			fmt.Println("Password ok")
		} else {
			fmt.Println("Bad password")
		}

		os.Exit(0)
	}
}

Running the program and passing in your possible password should result in either a Password OK or Bad Password response from the program:

t@m1 regexppass % go run main.go --p=23498aosethuaosthAT
Pass ok
t@m1 regexppass % go run main.go --p=2Aoeue             
Bad password
t@m1 regexppass % go run main.go --p=2AoeueEEEE
Bad password

Quiz Builder

Now, this project is the first in our queue at automating a mundane work task--your teacher friends will love you!

In this project, you'll build a quiz generator around the U.S. and its capitals. However, you will generally have the format of a quiz-building piece of software that can be changed to generate different sorts of quizzes. Maybe, you'll change it to do the capitals of your home country and its states.

package main

import (
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	capitals := map[string]string{
		"Alabama":        "Montgomery",
		"Alaska":         "Juneau",
		"Arizona":        "Phoenix",
		"Arkansas":       "Little Rock",
		"California":     "Sacramento",
		"Colorado":       "Denver",
		"Connecticut":    "Hartford",
		"Delaware":       "Dover",
		"Florida":        "Tallahassee",
		"Georgia":        "Atlanta",
		"Hawaii":         "Honolulu",
		"Idaho":          "Boise",
		"Illinois":       "Springfield",
		"Indiana":        "Indianapolis",
		"Iowa":           "Des Moines",
		"Kansas":         "Topeka",
		"Kentucky":       "Frankfort",
		"Louisiana":      "Baton Rouge",
		"Maine":          "Augusta",
		"Maryland":       "Annapolis",
		"Massachusetts":  "Boston",
		"Michigan":       "Lansing",
		"Minnesota":      "Saint Paul",
		"Mississippi":    "Jackson",
		"Missouri":       "Jefferson City",
		"Montana":        "Helena",
		"Nebraska":       "Lincoln",
		"Nevada":         "Carson City",
		"New Hampshire":  "Concord",
		"New Jersey":     "Trenton",
		"New Mexico":     "Santa Fe",
		"New York":       "Albany",
		"North Carolina": "Raleigh",
		"North Dakota":   "Bismarck",
		"Ohio":           "Columbus",
		"Oklahoma":       "Oklahoma City",
		"Oregon":         "Salem",
		"Pennsylvania":   "Harrisburg",
		"Rhode Island":   "Providence",
		"South Carolina": "Columbia",
		"South Dakota":   "Pierre",
		"Tennessee":      "Nashville",
		"Texas":          "Austin",
		"Utah":           "Salt Lake City",
		"Vermont":        "Montpelier",
		"Virginia":       "Richmond",
		"Washington":     "Olympia",
		"West Virginia":  "Charleston",
		"Wisconsin":      "Madison",
		"Wyoming":        "Cheyenne",
	}

	var states, capitalsItems []string
	for y, x := range capitals {
		capitalsItems = append(capitalsItems, x)
		states = append(states, y)
	}

	for i := 0; i < 35; i++ {
		// сreate the quiz text file
		str1 := "quiz_" + strconv.Itoa(i+1) + ".txt"
		quizFile, err := os.Create(str1)
		check(err)
		defer quizFile.Close()
		// create the answer key to the quiz
		str2 := "answer_key_" + strconv.Itoa(i+1) + ".txt"
		answerKeyFile, err := os.Create(str2)
		check(err)
		defer answerKeyFile.Close()

		// Create portion for students to fill out
		quizFile.WriteString("Student Number:\n\nName:\n\nDate:\n\n")
		str3 := "Quiz " + strconv.Itoa(i+1)
		quizFile.WriteString(strings.Repeat(" ", 20) + str3)
		quizFile.WriteString("\n\n")

		rand.Seed(time.Now().UnixNano())
		// mix of the States
		shuffle(states)

		// Iterate through and build the question out 
		for j := 0; j < 50; j++ {
			correctAnswer := capitals[states[j]]
			wrongAnswers := make([]string, len(capitalsItems))
			copy(wrongAnswers, capitalsItems)

			// shuffle wrong answers
			answNoCorrect := make([]string, len(wrongAnswers)-1)
			for l := 0; l < len(wrongAnswers); l++ {
				if wrongAnswers[l] == correctAnswer {
					copy(answNoCorrect, removeAtIndex(wrongAnswers, l))
				}
			}

			// create answer options A-D
			var answerOptions []string
			for l := 0; l < 3; l++ {
				answerOptions = append(answerOptions, answNoCorrect[l])
			}
			answerOptions = append(answerOptions, correctAnswer)
			shuffle(answerOptions)

			// create question
			str3 := strconv.Itoa(j+1) + " What is the Capital of " + states[j] + "?" + "\n"
			quizFile.WriteString(str3)
			strAbcd := "ABCD"
			for l := 0; l < 4; l++ {
				strAnsw := string(strAbcd[l]) + ". " + answerOptions[l] + "\n"
				quizFile.WriteString(strAnsw)
			}
			// make quiz and save it
			quizFile.WriteString("\n")

			// make answer key and save it
			strAnswerOk := ""
			for l := 0; l < len(answerOptions); l++ {
				if answerOptions[l] == correctAnswer {
					strAnswerOk += string(strAbcd[l])
				}
			}
			strCorAnsw := strconv.Itoa(j+1) + ". " + strAnswerOk + "\n"
			answerKeyFile.WriteString(strCorAnsw)
		}
	}
}

// helper functions for making quiz building easier
func check(e error) {
	if e != nil {
		panic(e)
	}
}

func shuffle(a []string) {
	for i := range a {
		j := rand.Intn(i + 1)
		a[i], a[j] = a[j], a[i]
	}
}

func removeAtIndex(source []string, index int) []string {
	lastIndex := len(source) - 1
	source[index], source[lastIndex] = source[lastIndex], source[index]
	return source[:lastIndex]
}

There you have it, the foundation of all the string knowledge you'll use to work with string data in files, clipboards, or anywhere else.

Chapter 7. String Manipulation