命令行应用是处理用户输入和输出的最简单方法之一。本章将重点介绍基于命令行的交互,如命令行参数、配置和环境变量。最后,我们将介绍一个在 Unix 和 Bash for Windows 中为文本输出着色的库。
使用本章中的配方,您应该能够处理预期和意外的用户输入。捕获和处理信号配方是用户可能向应用发送意外信号的一个示例,管道配方是与标志或命令行参数相比,接受用户输入的一个很好的替代方案。
ANSI 颜色配方有望为用户提供一些清理输出的示例。例如,在日志记录中,能够根据文本的用途为文本着色有时可以使大块文本明显更清晰。
在本章中,我们将介绍以下配方:
- 使用命令行标志
- 使用命令行参数
- 读取和设置环境变量
- 使用 TOML、YAML 和 JSON 进行配置
- 使用 Unix 管道
- 捕捉和处理信号
- ANSI 着色应用
为了继续本章中的所有配方,请按照以下步骤配置您的环境:
- 在您的操作系统上下载并安装 Go 1.12.6 或更高版本 https://golang.org/doc/install 。
- 打开终端或控制台应用,创建并导航到项目目录,如
~/projects/go-programming-cookbook
。我们的所有代码都将从此目录运行和修改。 - 将最新代码克隆到
~/projects/go-programming-cookbook-original
中,并从该目录开始工作,而不是手动键入示例:
$ git clone git@github.com:PacktPublishing/Go-Programming-Cookbook-Second-Edition.git go-programming-cookbook-original
flag
包使向 Go 应用添加命令行标志参数变得简单。它有一些缺点:为了添加标志的速记版本,您往往会复制大量代码,并且它们是按照帮助提示中的字母顺序排列的。有许多第三方库试图解决这些缺点,但本章将重点介绍标准库版本,而不是这些库。
这些步骤包括编写和运行应用:
- 从终端或控制台应用中,创建一个名为
~/projects/go-programming-cookbook/chapter2/flags
的新目录。 - 导航到此目录。
- 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/flags
您应该看到一个名为go.mod
的文件,其中包含以下内容:
module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/flags
- 从
~/projects/go-programming-cookbook-original/chapter2/flags
复制测试,或者以此为契机编写一些自己的代码! - 创建一个名为
flags.go
的文件,包含以下内容:
package main
import (
"flag"
"fmt"
)
// Config will be the holder for our flags
type Config struct {
subject string
isAwesome bool
howAwesome int
countTheWays CountTheWays
}
// Setup initializes a config from flags that
// are passed in
func (c *Config) Setup() {
// you can set a flag directly like so:
// var someVar = flag.String("flag_name", "default_val",
// "description")
// but in practice putting it in a struct is generally
// better longhand
flag.StringVar(&c.subject, "subject", "", "subject is a
string, it defaults to empty")
// shorthand
flag.StringVar(&c.subject, "s", "", "subject is a string,
it defaults to empty (shorthand)")
flag.BoolVar(&c.isAwesome, "isawesome", false, "is it
awesome or what?")
flag.IntVar(&c.howAwesome, "howawesome", 10, "how awesome
out of 10?")
// custom variable type
flag.Var(&c.countTheWays, "c", "comma separated list of
integers")
}
// GetMessage uses all of the internal
// config vars and returns a sentence
func (c *Config) GetMessage() string {
msg := c.subject
if c.isAwesome {
msg += " is awesome"
} else {
msg += " is NOT awesome"
}
msg = fmt.Sprintf("%s with a certainty of %d out of 10\. Let
me count the ways %s", msg, c.howAwesome,
c.countTheWays.String())
return msg
}
- 创建一个名为
custom.go
的文件,包含以下内容:
package main
import (
"fmt"
"strconv"
"strings"
)
// CountTheWays is a custom type that
// we'll read a flag into
type CountTheWays []int
func (c *CountTheWays) String() string {
result := ""
for _, v := range *c {
if len(result) > 0 {
result += " ... "
}
result += fmt.Sprint(v)
}
return result
}
// Set will be used by the flag package
func (c *CountTheWays) Set(value string) error {
values := strings.Split(value, ",")
for _, v := range values {
i, err := strconv.Atoi(v)
if err != nil {
return err
}
*c = append(*c, i)
}
return nil
}
- 运行以下命令:
$ go mod tidy
- 创建一个名为
main.go
的文件,包含以下内容:
package main
import (
"flag"
"fmt"
)
func main() {
// initialize our setup
c := Config{}
c.Setup()
// generally call this from main
flag.Parse()
fmt.Println(c.GetMessage())
}
- 在命令行上运行以下命令:
$ go build $ ./flags -h
- 试试这些和其他一些论点;您应该看到以下输出:
$ go build
$ ./flags -h
Usage of ./flags:
-c value
comma separated list of integers
-howawesome int
how awesome out of 10? (default 10)
-isawesome
is it awesome or what? (default false)
-s string
subject is a string, it defaults to empty (shorthand)
-subject string
subject is a string, it defaults to empty
$ ./flags -s Go -isawesome -howawesome 10 -c 1,2,3
Go is awesome with a certainty of 10 out of 10\. Let me count
the ways 1 ... 2 ... 3
- 如果您复制或编写了自己的测试,请转到一个目录并运行
go test
,并确保所有测试都通过。
本食谱试图展示flag
包装的大多数常见用法。它显示自定义变量类型、各种内置变量、速记标志,以及将所有标志写入公共结构。这是第一个需要 main 函数的配方,因为 flag(flag.Parse()
的主要用法应该从 main 调用。因此,省略了普通示例目录。
此应用的示例用法显示,您自动获取-h
以获取包含的标志列表。需要注意的其他一些事情是布尔标志,它们在没有参数的情况下被调用,并且标志顺序并不重要。
flag
包是一种快速构建命令行应用输入结构的方法,并提供了一种灵活的方法来指定预先用户输入,例如设置日志级别或应用的详细程度。在使用命令行参数配方中,我们将探索标志集,并使用参数在它们之间切换。
上一个配方中的标志是命令行参数的一种类型。本章将通过构造支持嵌套子命令的命令来扩展这些参数的其他用途。这将演示标志集,并使用传递到应用中的位置参数。
与前面的配方一样,此配方需要一个主函数才能运行。有许多第三方软件包处理复杂的嵌套参数和标志,但我们将研究如何仅使用标准库来实现这一点。
这些步骤包括编写和运行应用:
- 从终端或控制台应用中,创建一个名为
~/projects/go-programming-cookbook/chapter2/cmdargs
的新目录。 - 导航到此目录。
- 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/cmdargs
您应该看到一个名为go.mod
的文件,其中包含以下内容:
module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/cmdargs
- 从
~/projects/go-programming-cookbook-original/chapter2/cmdargs
复制测试,或者以此为契机编写一些自己的代码! - 创建一个名为
cmdargs.go
的文件,包含以下内容:
package main
import (
"flag"
"fmt"
"os"
)
const version = "1.0.0"
const usage = `Usage:
%s [command]
Commands:
Greet
Version
`
const greetUsage = `Usage:
%s greet name [flag]
Positional Arguments:
name
the name to greet
Flags:
`
// MenuConf holds all the levels
// for a nested cmd line argument
type MenuConf struct {
Goodbye bool
}
// SetupMenu initializes the base flags
func (m *MenuConf) SetupMenu() *flag.FlagSet {
menu := flag.NewFlagSet("menu", flag.ExitOnError)
menu.Usage = func() {
fmt.Printf(usage, os.Args[0])
menu.PrintDefaults()
}
return menu
}
// GetSubMenu return a flag set for a submenu
func (m *MenuConf) GetSubMenu() *flag.FlagSet {
submenu := flag.NewFlagSet("submenu", flag.ExitOnError)
submenu.BoolVar(&m.Goodbye, "goodbye", false, "Say goodbye
instead of hello")
submenu.Usage = func() {
fmt.Printf(greetUsage, os.Args[0])
submenu.PrintDefaults()
}
return submenu
}
// Greet will be invoked by the greet command
func (m *MenuConf) Greet(name string) {
if m.Goodbye {
fmt.Println("Goodbye " + name + "!")
} else {
fmt.Println("Hello " + name + "!")
}
}
// Version prints the current version that is
// stored as a const
func (m *MenuConf) Version() {
fmt.Println("Version: " + version)
}
- 创建一个名为
main.go
的文件,包含以下内容:
package main
import (
"fmt"
"os"
"strings"
)
func main() {
c := MenuConf{}
menu := c.SetupMenu()
if err := menu.Parse(os.Args[1:]); err != nil {
fmt.Printf("Error parsing params %s, error: %v", os.Args[1:], err)
return
}
// we use arguments to switch between commands
// flags are also an argument
if len(os.Args) > 1 {
// we don't care about case
switch strings.ToLower(os.Args[1]) {
case "version":
c.Version()
case "greet":
f := c.GetSubMenu()
if len(os.Args) < 3 {
f.Usage()
return
}
if len(os.Args) > 3 {
if err := f.Parse(os.Args[3:]); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing params %s, error: %v", os.Args[3:], err)
return
}
}
c.Greet(os.Args[2])
default:
fmt.Println("Invalid command")
menu.Usage()
return
}
} else {
menu.Usage()
return
}
}
- 运行
go build
。 - 运行以下命令并尝试其他几个参数组合:
$ ./cmdargs -h
Usage:
./cmdargs [command]
Commands:
Greet
Version
$./cmdargs version
Version: 1.0.0
$./cmdargs greet
Usage:
./cmdargs greet name [flag]
Positional Arguments:
name
the name to greet
Flags:
-goodbye
Say goodbye instead of hello
$./cmdargs greet reader
Hello reader!
$./cmdargs greet reader -goodbye
Goodbye reader!
- 如果您复制或编写了自己的测试,请转到一个目录并运行
go test
,并确保所有测试都通过。
标记集可用于设置预期参数、用法字符串等的独立列表。开发人员需要对多个参数执行验证,解析命令的正确参数子集,并定义使用字符串。这很容易出错,需要大量迭代才能完全正确。
flag
包使解析参数变得更加容易,并包含方便的方法来获取标志、参数等的数量。此配方演示了使用参数构造复杂命令行应用的基本方法,包括包级配置、所需的位置参数、多级命令使用,以及如何在需要时将这些内容拆分为多个文件或包。
环境变量是将状态传递到应用的另一种方式,而不仅仅是从文件中读入数据或通过命令行显式传递数据。本食谱将探索一些非常基本的环境变量获取和设置,然后与非常有用的第三方库envconfig
(一起使用 https://github.com/kelseyhightower/envconfig 。
我们将构建一个可以通过 JSON 或环境变量读取config
文件的应用。下一个食谱将探索其他格式,包括 TOML 和 YAML。
这些步骤包括编写和运行应用:
- 从终端或控制台应用中,创建一个名为
~/projects/go-programming-cookbook/chapter2/envvar
的新目录。 - 导航到此目录。
- 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/envvar
您应该看到一个名为go.mod
的文件,其中包含以下内容:
module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/envvar
- 从
~/projects/go-programming-cookbook-original/chapter2/envvar
复制测试,或者以此为契机编写一些自己的代码! - 创建一个名为
config.go
的文件,包含以下内容:
package envvar
import (
"encoding/json"
"os"
"github.com/kelseyhightower/envconfig"
"github.com/pkg/errors"
)
// LoadConfig will load files optionally from the json file
// stored at path, then will override those values based on the
// envconfig struct tags. The envPrefix is how we prefix our
// environment variables.
func LoadConfig(path, envPrefix string, config interface{})
error {
if path != "" {
err := LoadFile(path, config)
if err != nil {
return errors.Wrap(err, "error loading config from
file")
}
}
err := envconfig.Process(envPrefix, config)
return errors.Wrap(err, "error loading config from env")
}
// LoadFile unmarshalls a json file into a config struct
func LoadFile(path string, config interface{}) error {
configFile, err := os.Open(path)
if err != nil {
return errors.Wrap(err, "failed to read config file")
}
defer configFile.Close()
decoder := json.NewDecoder(configFile)
if err = decoder.Decode(config); err != nil {
return errors.Wrap(err, "failed to decode config file")
}
return nil
}
- 创建一个名为
example
的新目录并导航到它。 - 创建一个包含以下内容的
main.go
文件:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter2/envvar"
)
// Config will hold the config we
// capture from a json file and env vars
type Config struct {
Version string `json:"version" required:"true"`
IsSafe bool `json:"is_safe" default:"true"`
Secret string `json:"secret"`
}
func main() {
var err error
// create a temporary file to hold
// an example json file
tf, err := ioutil.TempFile("", "tmp")
if err != nil {
panic(err)
}
defer tf.Close()
defer os.Remove(tf.Name())
// create a json file to hold
// our secrets
secrets := `{
"secret": "so so secret"
}`
if _, err =
tf.Write(bytes.NewBufferString(secrets).Bytes());
err != nil {
panic(err)
}
// We can easily set environment variables
// as needed
if err = os.Setenv("EXAMPLE_VERSION", "1.0.0"); err != nil
{
panic(err)
}
if err = os.Setenv("EXAMPLE_ISSAFE", "false"); err != nil {
panic(err)
}
c := Config{}
if err = envvar.LoadConfig(tf.Name(), "EXAMPLE", &c);
err != nil {
panic(err)
}
fmt.Println("secrets file contains =", secrets)
// We can also read them
fmt.Println("EXAMPLE_VERSION =",
os.Getenv("EXAMPLE_VERSION"))
fmt.Println("EXAMPLE_ISSAFE =",
os.Getenv("EXAMPLE_ISSAFE"))
// The final config is a mix of json and environment
// variables
fmt.Printf("Final Config: %#v\n", c)
}
- 运行
go run main.go
。 - 您还可以运行以下命令:
go build ./example
- 您应该看到以下输出:
$ go run main.go
secrets file contains = {
"secret": "so so secret"
}
EXAMPLE_VERSION = 1.0.0
EXAMPLE_ISSAFE = false
Final Config: main.Config{Version:"1.0.0", IsSafe:false,
Secret:"so so secret"}
go.mod
文件可能会被更新,go.sum
文件现在应该出现在顶级配方目录中。- 如果您复制或编写了自己的测试,请转到一个目录并运行
go test
,并确保所有测试都通过。
使用os
包读取和写入环境变量非常简单。此配方使用的envconfig
第三方库是一种巧妙的方法,可以捕获环境变量,并使用struct
标记指定特定需求。
LoadConfig
函数是一种灵活的方式,可以从各种来源获取配置信息,而无需大量开销或太多额外依赖项。将主要的config
转换成 JSON 之外的另一种格式或者总是使用环境变量是很简单的。
另外,请注意错误的使用。我们将错误包装在该配方的整个代码中,以便在不丢失原始错误信息的情况下对错误进行注释。在第 4 章、Go中的错误处理中将对此进行详细说明。
使用第三方库支持多种配置格式。三种最流行的数据格式是 TOML、YAML 和 JSON。Go 可以支持 JSON 开箱即用,其他人则提供了关于如何封送/解封或对这些格式的数据进行编码/解码的线索。除了配置之外,这些格式还有许多好处,但本章将主要关注以配置结构的形式转换 Go 结构。本食谱将探索使用这些格式的基本输入和输出。
这些格式还提供了一个接口,Go 和用其他语言编写的应用可以通过该接口共享相同的配置。还有许多工具可以处理这些格式并简化它们的使用。
这些步骤包括编写和运行应用:
- 从终端或控制台应用中,创建一个名为
~/projects/go-programming-cookbook/chapter2/confformat
的新目录。 - 导航到此目录。
- 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/confformat
您应该看到一个名为go.mod
的文件,其中包含以下内容:
module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/confformat
- 从
~/projects/go-programming-cookbook-original/chapter2/confformat
复制测试,或者以此为契机编写一些自己的代码! - 创建一个名为
toml.go
的文件,包含以下内容:
package confformat
import (
"bytes"
"github.com/BurntSushi/toml"
)
// TOMLData is our common data struct
// with TOML struct tags
type TOMLData struct {
Name string `toml:"name"`
Age int `toml:"age"`
}
// ToTOML dumps the TOMLData struct to
// a TOML format bytes.Buffer
func (t *TOMLData) ToTOML() (*bytes.Buffer, error) {
b := &bytes.Buffer{}
encoder := toml.NewEncoder(b)
if err := encoder.Encode(t); err != nil {
return nil, err
}
return b, nil
}
// Decode will decode into TOMLData
func (t *TOMLData) Decode(data []byte) (toml.MetaData, error) {
return toml.Decode(string(data), t)
}
- 创建一个名为
yaml.go
的文件,包含以下内容:
package confformat
import (
"bytes"
"github.com/go-yaml/yaml"
)
// YAMLData is our common data struct
// with YAML struct tags
type YAMLData struct {
Name string `yaml:"name"`
Age int `yaml:"age"`
}
// ToYAML dumps the YAMLData struct to
// a YAML format bytes.Buffer
func (t *YAMLData) ToYAML() (*bytes.Buffer, error) {
d, err := yaml.Marshal(t)
if err != nil {
return nil, err
}
b := bytes.NewBuffer(d)
return b, nil
}
// Decode will decode into TOMLData
func (t *YAMLData) Decode(data []byte) error {
return yaml.Unmarshal(data, t)
}
- 创建一个名为
json.go
的文件,包含以下内容:
package confformat
import (
"bytes"
"encoding/json"
"fmt"
)
// JSONData is our common data struct
// with JSON struct tags
type JSONData struct {
Name string `json:"name"`
Age int `json:"age"`
}
// ToJSON dumps the JSONData struct to
// a JSON format bytes.Buffer
func (t *JSONData) ToJSON() (*bytes.Buffer, error) {
d, err := json.Marshal(t)
if err != nil {
return nil, err
}
b := bytes.NewBuffer(d)
return b, nil
}
// Decode will decode into JSONData
func (t *JSONData) Decode(data []byte) error {
return json.Unmarshal(data, t)
}
// OtherJSONExamples shows ways to use types
// beyond structs and other useful functions
func OtherJSONExamples() error {
res := make(map[string]string)
err := json.Unmarshal([]byte(`{"key": "value"}`), &res)
if err != nil {
return err
}
fmt.Println("We can unmarshal into a map instead of a
struct:", res)
b := bytes.NewReader([]byte(`{"key2": "value2"}`))
decoder := json.NewDecoder(b)
if err := decoder.Decode(&res); err != nil {
return err
}
fmt.Println("we can also use decoders/encoders to work with
streams:", res)
return nil
}
- 创建一个名为
marshal.go
的文件,包含以下内容:
package confformat
import "fmt"
// MarshalAll takes some data stored in structs
// and converts them to the various data formats
func MarshalAll() error {
t := TOMLData{
Name: "Name1",
Age: 20,
}
j := JSONData{
Name: "Name2",
Age: 30,
}
y := YAMLData{
Name: "Name3",
Age: 40,
}
tomlRes, err := t.ToTOML()
if err != nil {
return err
}
fmt.Println("TOML Marshal =", tomlRes.String())
jsonRes, err := j.ToJSON()
if err != nil {
return err
}
fmt.Println("JSON Marshal=", jsonRes.String())
yamlRes, err := y.ToYAML()
if err != nil {
return err
}
fmt.Println("YAML Marshal =", yamlRes.String())
return nil
}
- 创建一个名为
unmarshal.go
的文件,包含以下内容:
package confformat
import "fmt"
const (
exampleTOML = `name="Example1"
age=99
`
exampleJSON = `{"name":"Example2","age":98}`
exampleYAML = `name: Example3
age: 97
`
)
// UnmarshalAll takes data in various formats
// and converts them into structs
func UnmarshalAll() error {
t := TOMLData{}
j := JSONData{}
y := YAMLData{}
if _, err := t.Decode([]byte(exampleTOML)); err != nil {
return err
}
fmt.Println("TOML Unmarshal =", t)
if err := j.Decode([]byte(exampleJSON)); err != nil {
return err
}
fmt.Println("JSON Unmarshal =", j)
if err := y.Decode([]byte(exampleYAML)); err != nil {
return err
}
fmt.Println("Yaml Unmarshal =", y)
return nil
}
- 创建一个名为
example
的新目录并导航到它。 - 创建一个包含以下内容的
main.go
文件:
package main
import "github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter2/confformat"
func main() {
if err := confformat.MarshalAll(); err != nil {
panic(err)
}
if err := confformat.UnmarshalAll(); err != nil {
panic(err)
}
if err := confformat.OtherJSONExamples(); err != nil {
panic(err)
}
}
- 运行
go run main.go
。 - 您还可以运行以下命令:
$ go build $ ./example
- 您应该看到以下输出:
$ go run main.go
TOML Marshal = name = "Name1"
age = 20
JSON Marshal= {"name":"Name2","age":30}
YAML Marshal = name: Name3
age: 40
TOML Unmarshal = {Example1 99}
JSON Unmarshal = {Example2 98}
Yaml Unmarshal = {Example3 97}
We can unmarshal into a map instead of a struct: map[key:value]
we can also use decoders/encoders to work with streams:
map[key:value key2:value2]
go.mod
文件可能会被更新,go.sum
文件现在应该存在于顶级配方目录中。- 如果您复制或编写了自己的测试,请转到一个目录并运行
go test
。确保所有测试都通过。
这个方法为我们提供了一些示例,说明如何使用 TOML、YAML 和 JSON 解析器将原始数据写入 go 结构,并从中读取数据并转换为相应的格式。就像第 1 章、I/O 和文件系统中的方法一样,我们看到在[]byte
、string
、bytes.Buffer
和其他 I/O 接口之间快速切换是多么常见。
encoding/json
包在提供编码、封送和其他处理 JSON 格式的方法方面是最全面的。我们用ToFormat
函数抽象了这些类型,附加这样的多个方法将非常简单,这样我们就可以使用一个可以快速转换成或转换成这些类型的结构。
本食谱还涉及到结构标签及其使用。上一章也使用了这些方法,它们是向包和库提供有关如何处理结构中包含的数据的提示的常用方法。
当我们将一个程序的输出传递给另一个程序的输入时,Unix 管道非常有用。例如,请查看以下代码:
$ echo "test case" | wc -l
1
在 Go 应用中,可以使用os.Stdin
读入管道的左侧,其作用类似于文件描述符。为了演示这一点,此配方将在管道的左侧获取输入,并返回单词列表及其出现次数。这些单词将在空白处标记。
这些步骤包括编写和运行应用:
- 从终端或控制台应用中,创建一个名为
~/projects/go-programming-cookbook/chapter2/pipes
的新目录。 - 导航到此目录。
- 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/pipes
您应该看到一个名为go.mod
的文件,其中包含以下内容:
module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/pipes
- 从
~/projects/go-programming-cookbook-original/chapter2/pipes
复制测试,或者以此为契机编写一些自己的代码! - 创建一个名为
pipes.go
的文件,包含以下内容:
package main
import (
"bufio"
"fmt"
"io"
"os"
)
// WordCount takes a file and returns a map
// with each word as a key and it's number of
// appearances as a value
func WordCount(f io.Reader) map[string]int {
result := make(map[string]int)
// make a scanner to work on the file
// io.Reader interface
scanner := bufio.NewScanner(f)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
result[scanner.Text()]++
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
return result
}
func main() {
fmt.Printf("string: number_of_occurrences\n\n")
for key, value := range WordCount(os.Stdin) {
fmt.Printf("%s: %d\n", key, value)
}
}
- 运行
echo "some string" | go run pipes.go
。 - 您还可以运行以下命令:
$ go build echo "some string" | ./pipes
您应该看到以下输出:
$ echo "test case" | go run pipes.go
string: number_of_occurrences
test: 1
case: 1
$ echo "test case test" | go run pipes.go
string: number_of_occurrences
test: 2
case: 1
- 如果您复制或编写了自己的测试,请转到一个目录并运行
go test
。确保所有测试都通过。
在 Go 中使用管道非常简单,特别是如果您熟悉使用文件的话。例如,您可以使用第 1 章、I/O 和文件系统中的管道配方来创建tee应用(https://en.wikipedia.org/wiki/Tee_(命令),所有输入的内容立即写入stdout
和文件。
此配方使用扫描仪标记os.Stdin
文件对象的io.Reader
接口。您可以看到在完成所有读取之后必须如何检查错误。
信号是用户或操作系统杀死正在运行的应用的有用方法。有时,以比默认行为更优雅的方式处理这些信号是有意义的。Go 提供了一种捕捉和处理信号的机制。在本食谱中,我们将通过使用处理 Go 例程的信号来探索信号的处理。
这些步骤包括编写和运行应用:
- 从终端或控制台应用中,创建一个名为
~/projects/go-programming-cookbook/chapter2/signals
的新目录。 - 导航到此目录。
- 运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/signals
您应该看到一个名为go.mod
的文件,其中包含以下内容:
module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/signals
- 从
~/projects/go-programming-cookbook-original/chapter2/signals
复制测试,或者以此为契机编写一些自己的代码! - 创建一个名为
signals.go
的文件,包含以下内容:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
// CatchSig sets up a listener for
// SIGINT interrupts
func CatchSig(ch chan os.Signal, done chan bool) {
// block on waiting for a signal
sig := <-ch
// print it when it's received
fmt.Println("nsig received:", sig)
// we can set up handlers for all types of
// sigs here
switch sig {
case syscall.SIGINT:
fmt.Println("handling a SIGINT now!")
case syscall.SIGTERM:
fmt.Println("handling a SIGTERM in an entirely
different way!")
default:
fmt.Println("unexpected signal received")
}
// terminate
done <- true
}
func main() {
// initialize our channels
signals := make(chan os.Signal)
done := make(chan bool)
// hook them up to the signals lib
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
// if a signal is caught by this go routine
// it will write to done
go CatchSig(signals, done)
fmt.Println("Press ctrl-c to terminate...")
// the program blocks until someone writes to done
<-done
fmt.Println("Done!")
}
- 运行以下命令:
$ go build $ ./signals
- 尝试运行代码,然后按Ctrl+C。您应该看到以下内容:
$./signals
Press ctrl-c to terminate...
^C
sig received: interrupt
handling a SIGINT now!
Done!
- 尝试再次运行它。然后,从单独的终端确定 PID 并终止应用:
$./signals
Press ctrl-c to terminate...
# in a separate terminal
$ ps -ef | grep signals
501 30777 26360 0 5:00PM ttys000 0:00.00 ./signals
$ kill -SIGTERM 30777
# in the original terminal
sig received: terminated
handling a SIGTERM in an entirely different way!
Done!
- 如果您复制或编写了自己的测试,请转到一个目录并运行
go test
。确保所有测试都通过。
此配方使用了通道,在第 9 章、并行和并发中有更广泛的介绍。signal.Notify
功能需要一个发送信号通知的通道,以及我们关心的信号类型。然后,我们在 Go 例程中设置一个函数来处理传递给该函数的通道上的任何活动。一旦我们收到信号,我们就可以随心所欲地处理它。我们可以终止应用,用消息响应,并对不同的信号有不同的行为。kill
命令是测试向应用传递信号的好方法。
我们还使用done
通道阻止应用终止,直到收到信号。否则,程序将立即终止。对于长时间运行的应用(如 web 应用),这是不必要的。创建适当的信号处理例程来执行清理是非常有用的,尤其是在具有大量 Go 例程且持有大量状态的应用中。优雅关闭的一个实际例子可能是允许当前处理程序在不中途终止 HTTP 请求的情况下完成其 HTTP 请求。
ANSI 终端应用的着色由需要着色的文本部分前后的各种代码处理。本配方将探索一种基本的着色机制,将文本着色为红色或普通。有关完整的应用,请参见https://github.com/agtorre/gocolorize ,支持多种颜色和文本类型,并实现fmt.Formatter
接口,便于打印。
这些步骤包括编写和运行应用:
-
从终端或控制台应用中,创建一个名为
~/projects/go-programming-cookbook/chapter2/ansicolor
的新目录。 -
导航到此目录。
-
运行以下命令:
$ go mod init github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/ansicolor
您应该看到一个名为go.mod
的文件,其中包含以下内容:
module github.com/PacktPublishing/Go-Programming-Cookbook-Second-Edition/chapter2/ansicolor
- 从
~/projects/go-programming-cookbook-original/chapter2/ansicolor
复制测试,或者以此为契机编写一些自己的代码! - 创建一个名为
color.go
的文件,包含以下内容:
package ansicolor
import "fmt"
//Color of text
type Color int
const (
// ColorNone is default
ColorNone = iota
// Red colored text
Red
// Green colored text
Green
// Yellow colored text
Yellow
// Blue colored text
Blue
// Magenta colored text
Magenta
// Cyan colored text
Cyan
// White colored text
White
// Black colored text
Black Color = -1
)
// ColorText holds a string and its color
type ColorText struct {
TextColor Color
Text string
}
func (r *ColorText) String() string {
if r.TextColor == ColorNone {
return r.Text
}
value := 30
if r.TextColor != Black {
value += int(r.TextColor)
}
return fmt.Sprintf("33[0;%dm%s33[0m", value, r.Text)
}
- 创建一个名为
example
的新目录并导航到它。 - 创建一个包含以下内容的
main.go
文件:
package main
import (
"fmt"
"github.com/PacktPublishing/
Go-Programming-Cookbook-Second-Edition/
chapter2/ansicolor"
)
func main() {
r := ansicolor.ColorText{
TextColor: ansicolor.Red,
Text: "I'm red!",
}
fmt.Println(r.String())
r.TextColor = ansicolor.Green
r.Text = "Now I'm green!"
fmt.Println(r.String())
r.TextColor = ansicolor.ColorNone
r.Text = "Back to normal..."
fmt.Println(r.String())
}
- 运行
go run main.go
。 - 您还可以运行以下命令:
$ go build $ ./example
- 如果您的终端支持 ANSI 着色格式,则应看到以下带有彩色文本的输出:
$ go run main.go
I'm red!
Now I'm green!
Back to normal...
- 如果您复制或编写了自己的测试,请转到一个目录并运行
go test
。确保所有测试都通过。
此应用使用结构来维护彩色文本的状态。在这种情况下,它存储文本的颜色和文本的值。最后一个字符串在调用String()
方法时呈现,该方法将返回彩色文本或纯文本,具体取决于存储在结构中的值。默认情况下,文本将是纯文本。