有没有更好的方法来解析这个地图?

Is there a better way to parse this Map?

Go 相当新,基本上在我正在编写的实际代码中,我打算从一个包含环境变量的文件中读取,即 API_KEY=XYZ。意味着我可以让它们不受版本控制。下面的解决方案 'works' 但我觉得可能有更好的方法。

最终目标是能够像这样访问文件中的元素 m["API_KEY"] 将打印 XYZ。这甚至可能已经存在,我正在重新发明轮子,我看到 Go 有环境变量,但它似乎并不是我特别想要的。

非常感谢您的帮助。

Playground

代码:

package main

import (
    "fmt"
    "strings"
)

var m = make(map[string]string)

func main() {

    text := `Var1=Value1
    Var2=Value2
    Var3=Value3`

    arr := strings.Split(text, "\n")

    for _, value := range arr {
        tmp := strings.Split(value, "=")
        m[strings.TrimSpace(tmp[0])] = strings.TrimSpace(tmp[1])
    }

    fmt.Println(m)

}

1- 您可以使用 encoding/csv 中的 csv.NewReader 使用 r.ReadAll() 一个函数调用 r.ReadAll() 阅读全部:

r.Comma = '='
r.TrimLeadingSpace = true

结果为 [][]string输入顺序被保留,在 The Go Playground 上尝试:

package main

import (
    "encoding/csv"
    "fmt"
    "strings"
)

func main() {
    text := `Var1=Value1
    Var2=Value2
    Var3=Value3`

    r := csv.NewReader(strings.NewReader(text))
    r.Comma = '='
    r.TrimLeadingSpace = true

    all, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Println(all)
}

输出:

[[Var1 Value1] [Var2 Value2] [Var3 Value3]]

2- 您可以微调 csv.ReadAll() 以将输出转换为地图,但 顺序未保留 ,请在 The Go Playground 上尝试:

package main

import (
    "encoding/csv"
    "fmt"
    "io"
    "strings"
)

func main() {
    text := `Var1=Value1
    Var2=Value2
    Var3=Value3`
    r := csv.NewReader(strings.NewReader(text))
    r.Comma = '='
    r.TrimLeadingSpace = true
    all, err := ReadAll(r)
    if err != nil {
        panic(err)
    }
    fmt.Println(all)
}

func ReadAll(r *csv.Reader) (map[string]string, error) {
    m := make(map[string]string)
    for {
        tmp, err := r.Read()
        if err == io.EOF {
            return m, nil
        }
        if err != nil {
            return nil, err
        }
        m[tmp[0]] = tmp[1]
    }
}

输出:

map[Var2:Value2 Var3:Value3 Var1:Value1]

首先,我建议阅读这个相关问题:How to handle configuration in Go

接下来,我真的会考虑以另一种格式存储您的配置。因为你提出的不是标准。它接近 Java's property file format (.properties),但即使 属性 文件也可能包含 Unicode 序列,因此您的代码不是有效的 .properties 格式解析器,因为它根本不处理 Unicode 序列。

相反,我建议使用 JSON,因此您可以使用 Go 或任何其他语言轻松解析它,并且有很多工具可以编辑 JSON 文本,而且它仍然是人类-友好。

使用 JSON 格式,将其解码为 map 只是一个函数调用:json.Unmarshal()。它可能看起来像这样:

text := `{"Var1":"Value1", "Var2":"Value2", "Var3":"Value3"}`

var m map[string]string
if err := json.Unmarshal([]byte(text), &m); err != nil {
    fmt.Println("Invalid config file:", err)
    return
}

fmt.Println(m)

输出(在 Go Playground 上尝试):

map[Var1:Value1 Var2:Value2 Var3:Value3]

json 包将为您处理格式化和转义,因此您不必担心这些。它还会为您检测并报告错误。另外 JSON 更灵活,您的配置可能包含数字、文本、数组等。所有这些都用于 "free" 只是因为您选择了 JSON 格式。

另一种流行的配置格式是 YAML, but the Go standard library does not include a YAML parser. See Go implementation github.com/go-yaml/yaml

如果您不想更改格式,那么我将只使用您发布的代码,因为它完全按照您的要求执行:逐行处理输入,然后解析 name = value 每行的一对。 而且它以一种清晰明了的方式做到了。为此目的使用 CSV 或任何其他 reader 是不好的,因为它们隐藏了引擎盖下的内容(它们有意且正确地隐藏了格式特定的细节和转换)。一个CSV reader 一个CSV reader 首先;即使您更改了制表符/逗号:它 解释某些转义序列,并可能为您提供与您在纯文本编辑器中看到的不同的数据。从您的角度来看,这是一种意外行为,但是嘿,您的输入是 而不是 CSV 格式,但您要求 reader 将其解释为 CSV!

我要添加到您的解决方案中的一项改进是使用 bufio.Scanner。它可用于逐行读取输入,并处理不同样式的换行序列。它可能看起来像这样:

text := `Var1=Value1
Var2=Value2
Var3=Value3`

scanner := bufio.NewScanner(strings.NewReader(text))

m := map[string]string{}
for scanner.Scan() {
    parts := strings.Split(scanner.Text(), "=")
    if len(parts) == 2 {
        m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
    }
}
if err := scanner.Err(); err != nil {
    fmt.Println("Error encountered:", err)
}

fmt.Println(m)

输出相同。在 Go Playground.

上试试

使用bufio.Scanner还有一个好处:bufio.NewScanner() accepts an io.Reader, the general interface for "all things being a source of bytes". This means if your config is stored in a file, you don't even have to read all the config into the memory, you can just open the file e.g. with os.Open() which returns a value of *os.File也实现了io.Reader,所以你可以直接将*os.File值传递给bufio.NewScanner()(等等bufio.Scanner 将从文件中读取,而不是像上例中那样从内存缓冲区中读取。