I have written two CLI application tutorials to buildGorolkatwithCocosse. I usefortune
As an input generator.
In this article, I will complete the pipeline trilogy throughgofortune
.
what iswealth, The first one?As href="Encyclopedia said, Fortune is a simple program that displays pseudo-random messages from the quote database.
Basically, it is a random quote generator.
Its history can be traced back to Unix version 7 (1979). It is still very powerful. Many Linux distributions have it pre-installed, and on OSX, you can usebrew install fortune
.
On some systems, when using the shell, it is used as a greeting or parting message.
href="Encyclopedia also says
Many people choose to use their wealthCowsayCommand to add more humor to the dialog.
that's me! Except I use minegocowsay
command.
Enough introduction,Let's build a wealth clone with Go.
The following is a breakdown of the functions of our program.
The location of the wealth folder depends on the system and distribution, they are build markers. I can hardcode it or use environment variables, but as an exercise, I will do a dirty thing and askfortune
By direct execution-f
Flag, output:
The first line of output contains the path to the fortunes folder.
package main
import (
“fmt”
“os/exec”
)
func main() {
out, err := exec.Command(“fortune”, “-f”).CombinedOutput()
if err != nil {
panic(err)
}
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(string(<span style="color:#a6e22e">out</span>))
}
This code snippet replicates the output exactly in my way. It looks likefortune -f
Write output to stderr, that's why I useCombinedOutput
To get bothstdout
withstderr
.
However, I only want the first row. How to do it? This will print all the outputstderr
Line by line:
package main
import (
“bufio”
“fmt”
“os/exec”
)
func main() {
fortuneCommand := exec.Command(“fortune”, “-f”)
pipe, err := fortuneCommand.StderrPipe()
if err != nil {
panic(err)
}
for outputStream.Scan() {
fmt.Println(outputStream.Text())
}
}
In order to get only the first row, I removed the for loop, and then scanned the first row:
package main
import (
“bufio”
“fmt”
“os/exec”
)
func main() {
fortuneCommand := exec.Command(“fortune”, “-f”)
pipe, err := fortuneCommand.StderrPipe()
if err != nil {
panic(err)
}
fortuneCommand.Start()
outputStream := bufio.NewScanner(pipe)
outputStream.Scan()
fmt.Println(outputStream.Text())
}
Now let's select that line and extract the path.
On my system, the first line of output is100.00% /usr/local/Cellar/fortune/9708/share/games/fortunes
. Let's make a substring starting from the first occurrence/
character:
line := outputStream.Text()
path := line[strings.Index(line, "/"):]
Now, I have a way to get rich. I can index the files found there. Have.dat
Binary files and plain text files. I will discard the binary file and thenoff/
Folder, which contains annoying luck.
Let us first index the files. I usepath/filepath
packageWalk
The method to iterate the file tree from the following locationsroot
. I use it insteadioutil.ReadDir()
Because we may have nested destiny folders. insideWalkFunc
visit
I discarded the .dat file usingfilepath.Ext()
, I discarded the folder file (e.g./off
, But does not include files in subfolders) and all annoying fortunes (located in the following locations)/off
, And then print the value of each remaining file.
func visit(path string, f os.FileInfo, err error) error {
if strings.Contains(path, "/off/") {
return nil
}
if filepath.Ext(path) == ".dat" {
return nil
}
if f.IsDir() {
return nil
}
files = append(files, path)
return nil
}
func main() {
fortuneCommand := exec.Command(“fortune”, “-f”)
pipe, err := fortuneCommand.StderrPipe()
if err != nil {
panic(err)
}
fortuneCommand.Start()
outputStream := bufio.NewScanner(pipe)
outputStream.Scan()
line := outputStream.Text()
root := line[strings.Index(line, “/”):]
<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">visit</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
panic(<span style="color:#a6e22e">err</span>)
}
}
Let's put these values in a slice so that we can choose a random value later: I defined afiles
Part of the string, I append it tovisit()
Features. At the end ofmain()
I print out the number of files I got.
package main
import (
“bufio”
“log”
“os”
“os/exec”
“path/filepath”
“strings”
)
var files []string
func visit(path string, f os.FileInfo, err error) error {
if err != nil {
log.Fatal(err)
}
if strings.Contains(path, “/off/”) {
return nil
}
if filepath.Ext(path) == “.dat” {
return nil
}
if f.IsDir() {
return nil
}
files = append(files, path)
return nil
}
func main() {
fortuneCommand := exec.Command(“fortune”, “-f”)
pipe, err := fortuneCommand.StderrPipe()
if err != nil {
panic(err)
}
fortuneCommand.Start()
outputStream := bufio.NewScanner(pipe)
outputStream.Scan()
line := outputStream.Text()
root := line[strings.Index(line, “/”):]
<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">visit</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
panic(<span style="color:#a6e22e">err</span>)
}
println(len(<span style="color:#a6e22e">files</span>))
}
I now useGo to random number generatorThe function to select random items from an array:
// Returns an int >= min, < max
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
func main() {
<span style="color:#75715e">//...
rand.Seed(time.Now().UnixNano())
i := randomInt(1, len(files))
randomFile := files[i]
println(randomFile)
}
Now, our program will print a random wealth file name every time it runs.
What I miss now is the luck in scanning the documents and then printing them randomly. In each file, quotes are used%
Sit alone on a line. I can easily detect this pattern and scan every quote in the array:
file, err := os.Open(randomFile)
if err != nil {
panic(err)
}
defer file.Close()
b, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
quotes := string(b)
quotesSlice := strings.Split(quotes, “%”)
j := randomInt(1, len(quotesSlice))
fmt.Print(quotesSlice[j])
This is not really effective method, because I want to scan the entire wealth file in a slice, and then select a random item, but it works as follows:
So this is our very basic final versionfortune
clone. It misses a lot of originalfortune
Order, but this is the beginning.
package main
import (
“bufio”
“fmt”
“io/ioutil”
“log”
“math/rand”
“os”
“os/exec”
“path/filepath”
“strings”
“time”
)
var files []string
// Returns an int >= min, < max
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
func visit(path string, f os.FileInfo, err error) error {
if err != nil {
log.Fatal(err)
}
if strings.Contains(path, “/off/”) {
return nil
}
if filepath.Ext(path) == “.dat” {
return nil
}
if f.IsDir() {
return nil
}
files = append(files, path)
return nil
}
func main() {
fortuneCommand := exec.Command(“fortune”, “-f”)
pipe, err := fortuneCommand.StderrPipe()
if err != nil {
panic(err)
}
fortuneCommand.Start()
outputStream := bufio.NewScanner(pipe)
outputStream.Scan()
line := outputStream.Text()
root := line[strings.Index(line, “/”):]
<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">visit</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Seed</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">UnixNano</span>())
<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">files</span>))
<span style="color:#a6e22e">randomFile</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">files</span>[<span style="color:#a6e22e">i</span>]
<span style="color:#a6e22e">file</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Open</span>(<span style="color:#a6e22e">randomFile</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">file</span>.<span style="color:#a6e22e">Close</span>()
<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadAll</span>(<span style="color:#a6e22e">file</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#a6e22e">quotes</span> <span style="color:#f92672">:=</span> string(<span style="color:#a6e22e">b</span>)
<span style="color:#a6e22e">quotesSlice</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(<span style="color:#a6e22e">quotes</span>, <span style="color:#e6db74">"%"</span>)
<span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">quotesSlice</span>))
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#a6e22e">quotesSlice</span>[<span style="color:#a6e22e">j</span>])
}
To summarize, I movevisit
As an inline function parameterfilepath.Walk
And movefiles
Become an internal local variablemain()
Instead of global file variables:
package main
import (
“bufio”
“fmt”
“io/ioutil”
“log”
“math/rand”
“os”
“os/exec”
“path/filepath”
“strings”
“time”
)
// Returns an int >= min, < max
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
func main() {
var files []string
<span style="color:#a6e22e">fortuneCommand</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">exec</span>.<span style="color:#a6e22e">Command</span>(<span style="color:#e6db74">"fortune"</span>, <span style="color:#e6db74">"-f"</span>)
<span style="color:#a6e22e">pipe</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fortuneCommand</span>.<span style="color:#a6e22e">StderrPipe</span>()
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#a6e22e">fortuneCommand</span>.<span style="color:#a6e22e">Start</span>()
<span style="color:#a6e22e">outputStream</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bufio</span>.<span style="color:#a6e22e">NewScanner</span>(<span style="color:#a6e22e">pipe</span>)
<span style="color:#a6e22e">outputStream</span>.<span style="color:#a6e22e">Scan</span>()
<span style="color:#a6e22e">line</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">outputStream</span>.<span style="color:#a6e22e">Text</span>()
<span style="color:#a6e22e">root</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">line</span>[<span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Index</span>(<span style="color:#a6e22e">line</span>, <span style="color:#e6db74">"/"</span>):]
<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">path</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">f</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">FileInfo</span>, <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) <span style="color:#66d9ef">error</span> {
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
}
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Contains</span>(<span style="color:#a6e22e">path</span>, <span style="color:#e6db74">"/off/"</span>) {
<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
}
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Ext</span>(<span style="color:#a6e22e">path</span>) <span style="color:#f92672">==</span> <span style="color:#e6db74">".dat"</span> {
<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
}
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">IsDir</span>() {
<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
}
<span style="color:#a6e22e">files</span> = append(<span style="color:#a6e22e">files</span>, <span style="color:#a6e22e">path</span>)
<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
})
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Seed</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">UnixNano</span>())
<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">files</span>))
<span style="color:#a6e22e">randomFile</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">files</span>[<span style="color:#a6e22e">i</span>]
<span style="color:#a6e22e">file</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Open</span>(<span style="color:#a6e22e">randomFile</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">file</span>.<span style="color:#a6e22e">Close</span>()
<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadAll</span>(<span style="color:#a6e22e">file</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#a6e22e">quotes</span> <span style="color:#f92672">:=</span> string(<span style="color:#a6e22e">b</span>)
<span style="color:#a6e22e">quotesSlice</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(<span style="color:#a6e22e">quotes</span>, <span style="color:#e6db74">"%"</span>)
<span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">quotesSlice</span>))
<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#a6e22e">quotesSlice</span>[<span style="color:#a6e22e">j</span>])
}
I can nowgo build; go install
And trilogygofortune
gocowsay
withgololcat
finished:
More tutorials:
- Use NGINX reverse proxy service Go service
- Copy structure in Go
- Basics of Go web server
- Sorting map types in Go
- In a nutshell
- Go to label description
- Start date and time format
- Use Go for JSON processing
- Variadic function
- Cheat sheet
- Go to the empty interface description
- Use VS Code and Delve to debug Go
- Named Go return parameter
- Generate random numbers and strings in Go
- File system structure of Go project
- Binary search algorithm in Go
- Use command line flags in Go
- GOPATH explained
- Use Go to build a command line application: lolcat
- Use Go to build CLI commands: Cowsay
- Use shell and tube in Go
- Go CLI Tutorial: Fortune Clone
- Use Go to list files in a folder
- Use Go to get a list of repositories from GitHub
- Go, append a short string to the file
- Go, convert the string to byte slices
- Use Go to visualize your local Git contributions
- Getting started with Go CPU and memory analysis
- Solve the "Does not support index" error in the Go program
- Measuring the execution time in the Go program
- Use Go to build a web crawler to detect duplicate titles
- Best Practice: Pointer or Value Receiver?
- Best practice: Should you use methods or functions?
- Go data structure: set
- Go to the map cheat sheet
- Generating the implementation of generic types in Go
- Go data structure: dictionary
- Go data structure: hash table
- Implement event listeners in "through the channel"
- Go data structure: stack
- Go data structure: queue
- Go data structure: binary search tree
- Go data structure: graphics
- Go data structure: linked list
- A complete guide to Go data structures
- Compare Go value
- Is Go object-oriented?
- Use SQL database in Go
- Use environment variables in Go
- Last tutorial: REST API supported by PostgreSQL
- Enable CORS on the Go web server
- Deploy Go application in Docker container
- Why Go is a powerful language to learn as a PHP developer
- Go and delete the io.Reader.ReadString newline
- To start, how to watch the changes and rebuild the program
- To count the months since the date
- Access HTTP POST parameters in Go