Go基础:理解Go中的命令行、环境变量

构建命令行应用程序并处理系统配置。

full lessons here👇:
https://programmerscareer.com/zh-cn/golang-basic-skill/

由AI生成,可能有错误,仅供参考

1.1 Command Line Applications

命令行应用程序或 CLI 应用程序允许我们与我们的程序交互在文本界面中。这可以非常有用,用于创建脚本、自动化、服务器应用程序或当您 SSH 到服务器时。

在 Go 中,您可以编写强大且高效的 CLI 应用程序,并且 Go 提供了多个库使其变得更容易。对于我们的示例,我们将主要使用“os”和“bufio” 库。

“os” 包提供了与操作系统交互的函数和类型,以便在平台独立的情况下进行交互。”bufio” 库用于缓冲 I/O 操作,可以帮助提高 I/O 操作的效率。

以下是一个简单的 CLI 应用程序示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"bufio"
"fmt"
"os"
)

func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Println("Enter your name:")
name, _ := reader.ReadString('\n')
fmt.Printf("Hello, %s!\n", name)
}

在这个脚本中,我们创建了一个新的读取器,包围标准输入(os.Stdin),提示用户输入姓名,直到遇到换行符,然后将欢迎信息打印到控制台。

1.2 Handling Command Line Arguments

命令行参数可以为用户提供灵活的方式来交互您的程序。它们可能指定输入、配置或其他运行时选项,以便您的程序可以使用。

在 Go 中,这些命令行参数通过 os.Args 变量可用,该变量是一个字符串切片。请注意,os.Args[0] 是程序名称本身。

以下是一个简单的示例,它将打印所有参数:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
"os"
)

func main() {
for i, arg := range os.Args {
fmt.Printf("Arg %d: %s\n", i, arg)
}
}

您可以使用以下方式运行它:

1
go run main.go firstArg secondArg

这将打印:

1
2
3
Arg 0: /tmp/go-build123456789/command-line-arguments/_obj/exe/main
Arg 1: firstArg
Arg 2: secondArg

从这里,我们可以使用参数来控制我们的应用程序。

1.3 Reading from and Writing to the Console

命令行参数虽然很有用,但有时我们需要更多交互式用户输入。这就是从标准输入读取时非常有用的情况。

我们已经在第一个主题中见过这个示例,其中我们从用户读取了一行输入。让我们探索一下,并开始介绍写入控制台的方式。

以下是一个示例脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"bufio"
"fmt"
"os"
)

func main() {
reader := bufio.NewReader(os.Stdin)

// 读取用户直到'\n'
fmt.Println("Please enter your name:")
name, _ := reader.ReadString('\n')
// 写入控制台
fmt.Printf("Hi there, %s\n", name)
// 读取整数值
fmt.Println("Enter your age:")
var age int
fmt.Scanf("%d", &age)
fmt.Printf("You are %d years old!\n", age)
}

这个脚本首先从用户读取了一行输入,提供了一个提示用户输入姓名的机会。然后,它确认姓名,然后提示用户输入年龄,并从控制台读取整数值。最后,它使用读取的值来显示年龄到用户。

您的任务是回顾这个脚本、运行它、玩转它,并想象其他方式可以从控制台读取或写入。

1.4 环境变量

环境变量是一种配置程序行为的优秀方式,根据它所运行的环境。它们可以存储值,如数据库连接、API密钥或任何其他您想在编译后的程序外部配置的值。

在 Go 中,可以使用 os 包轻松地读取和写入环境变量。请查看这个简单示例:

1
2
3
4
5
6
7
8
9
10
11
package main

import (
"fmt"
"os"
)

func main() {
os.Setenv("NAME", "John")
fmt.Println("Hello", os.Getenv("NAME")) // prints: Hello John
}

在上面的示例中,我们使用 os.Setenv 创建一个名为 NAME 的新环境变量。然后,使用 os.Getenv 检索该值并将其打印出来。

请注意,使用 os.Setenv 设置的环境变量仅在当前进程中可用(即您的程序)。一旦进程停止,该变量将不再存在。

这些变量特别有助于配置根据应用程序运行环境变化的值,如本地、 staging 或生产环境。

1.5 创建配置文件

应用程序的配置是调整应用程序行为的关键步骤。它帮助我们在不更改代码的情况下调整应用程序的行为。配置可以通过多种方式注入,但在本课程中,我们将创建 .env.yaml 配置文件,这些文件在许多应用程序中广泛使用。

让我们从 .env 文件开始。

Dotenv 文件

在 Go 中,常用的包用于读取 .env 文件是 godotenv。创建一个 .env 文件,并将以下内容添加到其中:

1
GREETING=Hello from an env file

然后,创建一个 Go 文件,并使用以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"github.com/joho/godotenv"
"os"
"log"
)

func main() {
err := godotenv.Load(".env")

if err != nil {
log.Fatalf("Error loading .env file")
}

greeting := os.Getenv("GREETING")
fmt.Println(greeting) // prints: Hello from an env file
}

在上面的脚本中,我们使用 godotenv.Load() 函数加载环境文件,然后使用 os.Getenv("GREETING") 检索该值并将其打印出来。运行 Go 脚本,并观察输出结果。

YAML 文件

YAML(YAML Ain’t Markup Language)是一种人类可读的数据序列化语言,通常用于配置文件。Go 有多个包用于解析 YAML,我们将使用最常用的包——go-yaml

首先,让我们创建一个 config.yaml 文件:

1
2
settings:
greeting: Hello from a yaml file

现在,我们将尝试从该文件中读取配置。在您的 Go 脚本中,使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
"log"
)

type conf struct {
Settings settings `yaml:"settings"`
}

type settings struct {
Greeting string `yaml:"greeting"`
}

func (c *conf) getConf() *conf {
yamlFile, err := ioutil.ReadFile("config.yaml")
if err != nil {
log.Printf("yamlFile.Get err #%v", err)
}

err = yaml.Unmarshal(yamlFile, c)
if err != nil {
log.Fatalf("Unmarshal: %v", err)
}

return c
}

func main() {
var c conf
c.getConf()
fmt.Println(c.Settings.Greeting) // prints: Hello from a yaml file
}

在上面的脚本中,我们首先定义 YAML 文件的结构。conf 类型说我们的根对象有一个 Settings 对象,settings 类型说 Settings 对象包含一个 Greeting 字符串。我们使用 ioutil.ReadFile 加载文件,然后使用 yaml.Unmarshal 解析该文件,并简单地打印出欢迎信息。

YAML 文件允许您创建更加复杂的结构,这可能对更大规模的应用程序非常有用。

希望这个练习能够帮助您更好地理解在 Go 中工作配置文件。记住,您主要使用这些文件来配置应用程序行为,而不是更改代码本身。

Topic 1.6: Viper 配置

Go 的标准库和许多外部包提供了管理环境变量和配置文件的工具,但是处理复杂配置的过程可能会很困难。

Viper 是一个功能强大的库,可以帮助我们处理以下内容:

  • JSON、TOML、YAML、HCL 和 Java 属性配置文件
  • 实时监控和重新读取配置文件
  • 从环境变量中读取
  • 从远程配置系统(etcd 或 Consul)中读取,并监控变化
  • 设置默认值
  • 创建别名

让我们初始化一个 Viper 对象,并将其设置为从环境变量中读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"github.com/spf13/viper"
"log"
)

func main() {
viper.SetEnvPrefix("myapp") // 自动将字符串转换为大写
viper.BindEnv("id")
id := viper.Get("id") // 不区分大小写设置
fmt.Println(id)
}

正如你所看到的,Viper 允许我们使用 Go 的内置 os 包来处理环境变量,但是更加简洁和不易出错。

现在,让我们假设我们有一个名为 config.yaml 的配置文件:

1
2
3
4
Port: 8080
Db:
Username: "test-user"
Password: "test-pass"

我们可以使用以下方式访问这些配置设置:

1
2
3
4
5
6
7
8
9
10
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
log.Fatalf("Error while reading config file %s", err)
}
fmt.Printf("The application is running on port: %d\n", viper.GetInt("Port"))
fmt.Printf("DB User: %s\n", viper.GetString("Db.Username"))
fmt.Printf("DB Pass: %s\n", viper.GetString("Db.Password"))

在上面的示例中,我们使用 viper.GetStringviper.GetInt 来获取我们的配置值。Viper 自动为我们提供正确的类型。

请尝试使用 Viper,并等待你准备好,我们可以继续构建一个完整的应用程序,该应用程序将使用到目前为止所学到的概念。

Topic 1.7: 完整应用程序 — CLI 应用程序

在本节中,我们将创建一个基于 Viper 的 CLI 应用程序,用于处理标志、环境变量和 YAML 配置文件。

我们的应用程序将是一个基本的 HTTP 服务器。让我们开始编写代码!

首先,让我们定义我们的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
type Configuration struct {
Server ServerConfiguration
Database DatabaseConfiguration
}

type ServerConfiguration struct {
Port int
}

type DatabaseConfiguration struct {
User string
Password string
}

为了从文件或环境变量中获取配置,我们将创建一个 LoadConfiguration 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func LoadConfiguration(configPaths []string) (Configuration, error) {
var conf Configuration
viper.SetEnvPrefix("myapp")
replacer := strings.NewReplacer(".", "_")
viper.SetEnvKeyReplacer(replacer)
viper.BindEnv("server.port")
viper.BindEnv("database.user")
viper.BindEnv("database.password")
viper.SetConfigName("config")
viper.SetConfigType("yaml")
for _, path := range configPaths {
viper.AddConfigPath(path)
}
if err := viper.ReadInConfig(); err != nil {
fmt.Fprintf(os.Stderr, "Error while reading config file %s", err)
}
return conf, nil
}

在这个函数中,我们将配置加载到 conf 变量中,然后返回该变量。

现在,让我们使用 LoadConfiguration 函数来启动我们的 HTTP 服务器:

1
2
3
4
5
6
7
8
9
func main() {
configPaths := []string{"/etc/myapp/", "$HOME/.myapp", "."}
config, _ := LoadConfiguration(configPaths)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to my website!")
})
fmt.Printf("Server running on port: %d\n", config.Server.Port)
http.ListenAndServe(fmt.Sprintf(":%d", config.Server.Port), nil)
}

在这个示例中,我们使用 LoadConfiguration 函数来加载配置,然后启动我们的 HTTP 服务器。

请注意,需要将 "myapp" 替换为你的应用程序名称,并将实际值替换为配置路径。

Topic 1.8: Review and Practice

我们已经在这门课程中走过了很长的一段路程。让我们回顾一下你所学到的关键点:

  1. Go 中的命令行应用程序:你已经理解了如何使用 Go 构建 CLI 应用程序,利用标准的“os”和“bufio”库。
  2. 处理命令行参数:你已经学习了如何使用 os 包管理命令行参数,以及了解标志、参数和选项。
  3. 控制台 I/O 操作:你已经掌握了使用 “os” 和 “bufio” 包从控制台读取和写入数据。
  4. 环境变量:我们讨论了如何使用 os 包读取和设置环境变量,并解释了这些环境变量的重要性。
  5. 创建配置文件:我们探索了创建 .env.yaml 文件来存储应用程序配置。
  6. 复杂配置使用 Viper:你学习了如何使用 Viper 包处理复杂配置。
  7. 完整的 CLI 应用程序:使用你所学到的知识,你构建了一款功能齐全的 CLI 应用程序,演示了标志处理和环境变量配置。

现在,让我们通过一些练习来测试你的知识:

  1. 对 CLI 应用程序中的标志、参数和选项进行扩展。为每个提供一个例子。
  2. 解释如何在 Go 中使用环境变量来构建 HTTP 服务器应用程序。你将存储什么类型的信息?
  3. 写一段代码,演示如何在 Go 应用程序中读取 .yaml 配置文件。
  4. 创建一个小型的 Go CLI 应用程序,它可以从命令行参数中获取用户名称,并向用户打招呼。

这些练习将帮助你巩固所学的概念。

Topic 1.9: Assessments

这个会话将评估你的理解和应用所学到的主题。不要担心,这些问题是为了测量你的理解,而不是要 trick 你。把它当作一个反思一切所学的机会。让我们开始:

  1. 命令行应用程序:解释什么是命令行界面(CLI),为什么我们选择构建 CLI 应用程序,而不是图形用户界面(GUI)。
  2. 处理命令行参数:编写一个 Go 程序,它可以接受命令行参数并打印它们。
  3. 控制台 I/O 操作:编写一个 Go 程序,它从控制台读取输入字符串,并将其打印回去。
  4. 环境变量:编写一个 Go 程序,它可以读取环境变量并打印其值。
  5. 配置文件:解释使用 .env.yaml 文件来存储配置的用途。
  6. Viper 配置:编写一个 Go 程序,它从 .yaml 文件中读取 config 值,使用 viper 库。
  7. 完整应用程序:考虑需要多个配置(如 API 密钥或 URL)的应用程序。如何使用你所学到的知识来管理这个?

请慢慢思考,回答问题时不要匆忙。记住,这是一个反思你所学的机会。

English post: https://programmerscareer.com/go-basic-14/
作者:Wesley Wei – Twitter Wesley Wei – Medium
注意:本文为作者原创,转载请注明出处。

Go Basic:理解Go中的测试和基准测试 Go基础:了解 HTTP Client, HTTP Server, Context, Processes, Signals, Exit in Go

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×