golang cli_Go CLI教程:财富克隆

golang cli

I’ve written two CLI app tutorials to build gololcat and gocowsay. In both I used fortune as the input generator.

我编写了两个CLI应用程序教程来构建gololcatgocowsay 。 在这两种方式中,我都将fortune用作输入生成器。

In this article, I’ll complete the pipe triology with gofortune.

在本文中,我将使用gofortune完成管道gofortune

What is fortune, first? As Wikipedia says, Fortune is a simple program that display a pseudorandom message from a database of quotations.

首先是什么运气正如Wikipedia所说 ,Fortune是一个简单的程序,可显示来自报价数据库的伪随机消息。

Basically, a random quote generator.

基本上,是一个随机报价生成器。

It has a very long history dating back to Unix Version 7 (1979). It’s still going strong. Many Linux distributions preinstall it, and on OSX you can install it using brew install fortune.

它的历史可以追溯到Unix版本7(1979)。 它仍然很强大。 许多Linux发行版都预先安装了它,并且可以在OSX上使用brew install fortune来安装它。

On some systems, it’s used as a greeting or parting message when using shells.

在某些系统上,使用外壳程序时,它将用作问候或离别消息。

Wikipedia also says

维基百科还说

Many people choose to pipe fortune into the cowsay command, to add more humor to the dialog.

许多人选择将财富运用于Cowsay命令中,以增加对话框的幽默感。

That’s me! Except I use my gocowsay command.

那是我! 除了我使用gocowsay命令。

Enough with the intro, let’s build a fortune clone with Go.

足够介绍之后, 让我们使用Go构建一个财富克隆

Here’s a breakdown of what our program will do.

这是我们程序的功能细分。

The fortunes folder location depends on the system and distribution, being a build flag. I could hardcode it or use an environment variable but as an exercise, I’ll do a dirty thing and ask fortune directly, by executing it with the -f flag, which outputs:

财富文件夹的位置取决于系统和发行版,它们是构建标记。 我可以对其进行硬编码,也可以使用环境变量,但是作为练习,我将通过-f标志执行它来做一件肮脏的事情,并直接请求fortune ,该标志将输出:

The first line of the output contains the path of the fortunes folder.

输出的第一行包含fortunes文件夹的路径。

package main

import (
	"fmt"
	"os/exec"
)

func main() {
	out, err := exec.Command("fortune", "-f").CombinedOutput()
	if err != nil {
		panic(err)
	}

	fmt.Println(string(out))
}

This snippet replicates the output exactly as I got it. It seems that fortune -f writes the output to stderr, that’s why I used CombinedOutput, to get both stdout and stderr.

此代码段完全按照我的方式复制输出。 似乎fortune -f将输出写入stderr,这就是为什么我使用CombinedOutput来获取stdoutstderr

But, I just want the first line. How to do it? This prints all the output of stderr line-by-line:

但是,我只想要第一行。 怎么做? 这将逐行打印stderr所有输出:

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())
	}
}

To get just the first line, I remove the for loop, and just scan the first line:

为了只获得第一行,我删除了for循环,然后扫描第一行:

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 pick that line and extract the path.

现在,我们选择那条线并提取路径。

On my system the first line of the output is 100.00% /usr/local/Cellar/fortune/9708/share/games/fortunes. Let’s make a substring starting from the first occurrence of the / char:

在我的系统上,输出的第一行是100.00% /usr/local/Cellar/fortune/9708/share/games/fortunes 。 让我们从第一次出现的/ char开始创建一个子字符串:

line := outputStream.Text()
path := line[strings.Index(line, "/"):]

Now I have the path of the fortunes. I can index the files found in there. There are .dat binary files, and plain text files. I’m going to discard the binary files, and the off/ folder altogether, which contains offensive fortunes.

现在,我有了发财之路。 我可以索引在那里找到的文件。 有.dat二进制文件和纯文本文件。 我将舍弃二进制文件和off/文件夹,其中包含令人讨厌的财富。

Let’s first index the files. I use the path/filepath package Walk method to iterate the file tree starting from root. I use it instead of ioutil.ReadDir() because we might have nested folders of fortunes. In the WalkFunc visit I discard .dat files using filepath.Ext(), I discard the folder files (e.g. /off, but not the files in subfolders) and all the offensive fortunes, conveniently located under /off, and I print the value of each remaining file.

首先让我们为文件建立索引。 我使用path/filepathWalk方法来迭代从root开始的文件树。 我用它代替ioutil.ReadDir()因为我们可能有嵌套的财富文件夹。 在WalkFunc visit我使用filepath.Ext()丢弃.dat文件,我丢弃了文件夹文件(例如/off ,但不包含子文件夹中的文件)和所有令人讨厌的运气,它们位于/off下,非常方便,我打印了值每个剩余文件。

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, "/"):]

	err = filepath.Walk(root, visit)
	if err != nil {
		panic(err)
	}
}

Let’s put those values in a slice, so I can later pick a random one: I define a files slice of strings and I append to that in the visit() function. At the end of main() I print the number of files I got.

让我们将这些值放在一个片中,以便以后可以选择一个随机值:我定义了一个字符串files片,并将其附加到visit()函数中。 在main()的末尾,我打印得到的文件数。

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, "/"):]

	err = filepath.Walk(root, visit)
	if err != nil {
		panic(err)
	}

	println(len(files))
}

I now use the Go random number generator functionality to pick a random item from the array:

现在,我使用Go随机数生成器功能从数组中选择一个随机项:

// Returns an int >= min, < max
func randomInt(min, max int) int {
	return min + rand.Intn(max-min)
}

func main() {

    //...

    rand.Seed(time.Now().UnixNano())
	i := randomInt(1, len(files))
	randomFile := files[i]
	println(randomFile)
}

Our program now prints a random fortune filename on every run.

现在,我们的程序在每次运行时都会打印一个随机的财富文件名。

What I miss now is scanning the fortunes in a file, and printing a random one. In each file, quotes are separated by a % sitting on a line on its own. I can easily detect this pattern and scan every quote in an 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 efficient, as I’m scanning the entire fortune file in a slice and then I pick a random item, but it works:

这并不是真正有效,因为我要扫描一个切片中的整个财富文件,然后选择一个随机项目,但是它可以工作:

So, here’s the final version of our very basic fortune clone. It misses a lot of the original fortune command, but it’s a start.

因此,这是我们非常基本的fortune克隆的最终版本。 它错过了很多原始的fortune命令,但这只是一个开始。

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, "/"):]

	err = filepath.Walk(root, visit)
	if err != nil {
		panic(err)
	}

	rand.Seed(time.Now().UnixNano())
	i := randomInt(1, len(files))
	randomFile := files[i]

	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])
}

Wrapping up, I move visit as an inline function argument of filepath.Walk and move files to be a local variable inside main() instead of a global file variable:

总结起来,我将visit作为filepath.Walk的内联函数参数移动。将files移动到main()内的局部变量而不是全局文件变量中:

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

	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, "/"):]

	err = filepath.Walk(root, func(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
	})
	if err != nil {
		panic(err)
	}

	rand.Seed(time.Now().UnixNano())
	i := randomInt(1, len(files))
	randomFile := files[i]

	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])
}

I can now go build; go install and the triology gofortune gocowsay and gololcat is completed:

我现在go build; go install go build; go install然后完成三部曲gofortune gocowsaygololcat

翻译自: https://flaviocopes.com/go-tutorial-fortune/

golang cli

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值