Skip to content

Latest commit

 

History

History
1250 lines (956 loc) · 38.5 KB

File metadata and controls

1250 lines (956 loc) · 38.5 KB

二、命令行工具

命令行应用是处理用户输入和输出的最简单方法之一。本章将重点介绍基于命令行的交互,如命令行参数、配置和环境变量。最后,我们将介绍一个在 Unix 和 Bash for Windows 中为文本输出着色的库。

使用本章中的配方,您应该能够处理预期和意外的用户输入。捕获和处理信号配方是用户可能向应用发送意外信号的一个示例,管道配方是与标志或命令行参数相比,接受用户输入的一个很好的替代方案。

ANSI 颜色配方有望为用户提供一些清理输出的示例。例如,在日志记录中,能够根据文本的用途为文本着色有时可以使大块文本明显更清晰。

在本章中,我们将介绍以下配方:

  • 使用命令行标志
  • 使用命令行参数
  • 读取和设置环境变量
  • 使用 TOML、YAML 和 JSON 进行配置
  • 使用 Unix 管道
  • 捕捉和处理信号
  • ANSI 着色应用

技术要求

为了继续本章中的所有配方,请按照以下步骤配置您的环境:

  1. 在您的操作系统上下载并安装 Go 1.12.6 或更高版本 https://golang.org/doc/install
  2. 打开终端或控制台应用,创建并导航到项目目录,如~/projects/go-programming-cookbook。我们的所有代码都将从此目录运行和修改。
  3. 将最新代码克隆到~/projects/go-programming-cookbook-original中,并从该目录开始工作,而不是手动键入示例:
$ git clone git@github.com:PacktPublishing/Go-Programming-Cookbook-Second-Edition.git go-programming-cookbook-original

使用命令行标志

flag包使向 Go 应用添加命令行标志参数变得简单。它有一些缺点:为了添加标志的速记版本,您往往会复制大量代码,并且它们是按照帮助提示中的字母顺序排列的。有许多第三方库试图解决这些缺点,但本章将重点介绍标准库版本,而不是这些库。

怎么做。。。

这些步骤包括编写和运行应用:

  1. 从终端或控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter2/flags的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ 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    
  1. ~/projects/go-programming-cookbook-original/chapter2/flags复制测试,或者以此为契机编写一些自己的代码!
  2. 创建一个名为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
        }
  1. 创建一个名为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
        }
  1. 运行以下命令:
$ go mod tidy
  1. 创建一个名为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())
        }
  1. 在命令行上运行以下命令:
$ go build $ ./flags -h
  1. 试试这些和其他一些论点;您应该看到以下输出:
$ 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
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test,并确保所有测试都通过。

它是如何工作的。。。

本食谱试图展示flag包装的大多数常见用法。它显示自定义变量类型、各种内置变量、速记标志,以及将所有标志写入公共结构。这是第一个需要 main 函数的配方,因为 flag(flag.Parse()的主要用法应该从 main 调用。因此,省略了普通示例目录。

此应用的示例用法显示,您自动获取-h以获取包含的标志列表。需要注意的其他一些事情是布尔标志,它们在没有参数的情况下被调用,并且标志顺序并不重要。

flag包是一种快速构建命令行应用输入结构的方法,并提供了一种灵活的方法来指定预先用户输入,例如设置日志级别或应用的详细程度。在使用命令行参数配方中,我们将探索标志集,并使用参数在它们之间切换。

使用命令行参数

上一个配方中的标志是命令行参数的一种类型。本章将通过构造支持嵌套子命令的命令来扩展这些参数的其他用途。这将演示标志集,并使用传递到应用中的位置参数。

与前面的配方一样,此配方需要一个主函数才能运行。有许多第三方软件包处理复杂的嵌套参数和标志,但我们将研究如何仅使用标准库来实现这一点。

怎么做。。。

这些步骤包括编写和运行应用:

  1. 从终端或控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter2/cmdargs的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ 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   
  1. ~/projects/go-programming-cookbook-original/chapter2/cmdargs复制测试,或者以此为契机编写一些自己的代码!
  2. 创建一个名为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)
        }
  1. 创建一个名为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
  }
}
  1. 运行go build
  2. 运行以下命令并尝试其他几个参数组合:
$ ./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!
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test,并确保所有测试都通过。

它是如何工作的。。。

标记集可用于设置预期参数、用法字符串等的独立列表。开发人员需要对多个参数执行验证,解析命令的正确参数子集,并定义使用字符串。这很容易出错,需要大量迭代才能完全正确。

flag包使解析参数变得更加容易,并包含方便的方法来获取标志、参数等的数量。此配方演示了使用参数构造复杂命令行应用的基本方法,包括包级配置、所需的位置参数、多级命令使用,以及如何在需要时将这些内容拆分为多个文件或包。

读取和设置环境变量

环境变量是将状态传递到应用的另一种方式,而不仅仅是从文件中读入数据或通过命令行显式传递数据。本食谱将探索一些非常基本的环境变量获取和设置,然后与非常有用的第三方库envconfig一起使用 https://github.com/kelseyhightower/envconfig

我们将构建一个可以通过 JSON 或环境变量读取config文件的应用。下一个食谱将探索其他格式,包括 TOML 和 YAML。

怎么做。。。

这些步骤包括编写和运行应用:

  1. 从终端或控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter2/envvar的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ 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   
  1. ~/projects/go-programming-cookbook-original/chapter2/envvar复制测试,或者以此为契机编写一些自己的代码!
  2. 创建一个名为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
        }
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个包含以下内容的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)
        }
  1. 运行go run main.go
  2. 您还可以运行以下命令:
go build ./example
  1. 您应该看到以下输出:
$ 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"}
  1. go.mod文件可能会被更新,go.sum文件现在应该出现在顶级配方目录中。
  2. 如果您复制或编写了自己的测试,请转到一个目录并运行go test,并确保所有测试都通过。

它是如何工作的。。。

使用os包读取和写入环境变量非常简单。此配方使用的envconfig第三方库是一种巧妙的方法,可以捕获环境变量,并使用struct标记指定特定需求。

LoadConfig函数是一种灵活的方式,可以从各种来源获取配置信息,而无需大量开销或太多额外依赖项。将主要的config转换成 JSON 之外的另一种格式或者总是使用环境变量是很简单的。

另外,请注意错误的使用。我们将错误包装在该配方的整个代码中,以便在不丢失原始错误信息的情况下对错误进行注释。在第 4 章Go中的错误处理中将对此进行详细说明。

使用 TOML、YAML 和 JSON 进行配置

使用第三方库支持多种配置格式。三种最流行的数据格式是 TOML、YAML 和 JSON。Go 可以支持 JSON 开箱即用,其他人则提供了关于如何封送/解封或对这些格式的数据进行编码/解码的线索。除了配置之外,这些格式还有许多好处,但本章将主要关注以配置结构的形式转换 Go 结构。本食谱将探索使用这些格式的基本输入和输出。

这些格式还提供了一个接口,Go 和用其他语言编写的应用可以通过该接口共享相同的配置。还有许多工具可以处理这些格式并简化它们的使用。

怎么做。。。

这些步骤包括编写和运行应用:

  1. 从终端或控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter2/confformat的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ 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   
  1. ~/projects/go-programming-cookbook-original/chapter2/confformat复制测试,或者以此为契机编写一些自己的代码!
  2. 创建一个名为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)
        }
  1. 创建一个名为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)
        }
  1. 创建一个名为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
        }
  1. 创建一个名为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
        }
  1. 创建一个名为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
            }
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个包含以下内容的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)
            }
        }
  1. 运行go run main.go
  2. 您还可以运行以下命令:
$ go build $ ./example
  1. 您应该看到以下输出:
$ 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]
  1. go.mod文件可能会被更新,go.sum文件现在应该存在于顶级配方目录中。
  2. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

这个方法为我们提供了一些示例,说明如何使用 TOML、YAML 和 JSON 解析器将原始数据写入 go 结构,并从中读取数据并转换为相应的格式。就像第 1 章I/O 和文件系统中的方法一样,我们看到在[]bytestringbytes.Buffer和其他 I/O 接口之间快速切换是多么常见。

encoding/json包在提供编码、封送和其他处理 JSON 格式的方法方面是最全面的。我们用ToFormat函数抽象了这些类型,附加这样的多个方法将非常简单,这样我们就可以使用一个可以快速转换成或转换成这些类型的结构。

本食谱还涉及到结构标签及其使用。上一章也使用了这些方法,它们是向包和库提供有关如何处理结构中包含的数据的提示的常用方法。

使用 Unix 管道

当我们将一个程序的输出传递给另一个程序的输入时,Unix 管道非常有用。例如,请查看以下代码:

$ echo "test case" | wc -l
 1

在 Go 应用中,可以使用os.Stdin读入管道的左侧,其作用类似于文件描述符。为了演示这一点,此配方将在管道的左侧获取输入,并返回单词列表及其出现次数。这些单词将在空白处标记。

怎么做。。。

这些步骤包括编写和运行应用:

  1. 从终端或控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter2/pipes的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ 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   
  1. ~/projects/go-programming-cookbook-original/chapter2/pipes复制测试,或者以此为契机编写一些自己的代码!
  2. 创建一个名为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)
            }
        }
  1. 运行echo "some string" | go run pipes.go
  2. 您还可以运行以下命令:
$ 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
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

在 Go 中使用管道非常简单,特别是如果您熟悉使用文件的话。例如,您可以使用第 1 章I/O 和文件系统中的管道配方来创建tee应用(https://en.wikipedia.org/wiki/Tee_(命令),所有输入的内容立即写入stdout和文件。

此配方使用扫描仪标记os.Stdin文件对象的io.Reader接口。您可以看到在完成所有读取之后必须如何检查错误。

捕捉和处理信号

信号是用户或操作系统杀死正在运行的应用的有用方法。有时,以比默认行为更优雅的方式处理这些信号是有意义的。Go 提供了一种捕捉和处理信号的机制。在本食谱中,我们将通过使用处理 Go 例程的信号来探索信号的处理。

怎么做。。。

这些步骤包括编写和运行应用:

  1. 从终端或控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter2/signals的新目录。
  2. 导航到此目录。
  3. 运行以下命令:
$ 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   
  1. ~/projects/go-programming-cookbook-original/chapter2/signals复制测试,或者以此为契机编写一些自己的代码!
  2. 创建一个名为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!")

        }
  1. 运行以下命令:
$ go build $ ./signals
  1. 尝试运行代码,然后按Ctrl+C。您应该看到以下内容:
$./signals
Press ctrl-c to terminate...
^C
sig received: interrupt
handling a SIGINT now!
Done!
  1. 尝试再次运行它。然后,从单独的终端确定 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!
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

此配方使用了通道,在第 9 章并行和并发中有更广泛的介绍。signal.Notify功能需要一个发送信号通知的通道,以及我们关心的信号类型。然后,我们在 Go 例程中设置一个函数来处理传递给该函数的通道上的任何活动。一旦我们收到信号,我们就可以随心所欲地处理它。我们可以终止应用,用消息响应,并对不同的信号有不同的行为。kill命令是测试向应用传递信号的好方法。

我们还使用done通道阻止应用终止,直到收到信号。否则,程序将立即终止。对于长时间运行的应用(如 web 应用),这是不必要的。创建适当的信号处理例程来执行清理是非常有用的,尤其是在具有大量 Go 例程且持有大量状态的应用中。优雅关闭的一个实际例子可能是允许当前处理程序在不中途终止 HTTP 请求的情况下完成其 HTTP 请求。

ANSI 着色应用

ANSI 终端应用的着色由需要着色的文本部分前后的各种代码处理。本配方将探索一种基本的着色机制,将文本着色为红色或普通。有关完整的应用,请参见https://github.com/agtorre/gocolorize ,支持多种颜色和文本类型,并实现fmt.Formatter接口,便于打印。

怎么做。。。

这些步骤包括编写和运行应用:

  1. 从终端或控制台应用中,创建一个名为~/projects/go-programming-cookbook/chapter2/ansicolor的新目录。

  2. 导航到此目录。

  3. 运行以下命令:

$ 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   
  1. ~/projects/go-programming-cookbook-original/chapter2/ansicolor复制测试,或者以此为契机编写一些自己的代码!
  2. 创建一个名为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)
        }
  1. 创建一个名为example的新目录并导航到它。
  2. 创建一个包含以下内容的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())
        }
  1. 运行go run main.go
  2. 您还可以运行以下命令:
$ go build $ ./example
  1. 如果您的终端支持 ANSI 着色格式,则应看到以下带有彩色文本的输出:
$ go run main.go
I'm red!
Now I'm green!
Back to normal...
  1. 如果您复制或编写了自己的测试,请转到一个目录并运行go test。确保所有测试都通过。

它是如何工作的。。。

此应用使用结构来维护彩色文本的状态。在这种情况下,它存储文本的颜色和文本的值。最后一个字符串在调用String()方法时呈现,该方法将返回彩色文本或纯文本,具体取决于存储在结构中的值。默认情况下,文本将是纯文本。