Echo text to a file in golang

huangapple go评论128阅读模式
英文:

Echo text to a file in golang

问题

在Golang中,可以使用类似下面的bash代码的方式进行操作:

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. )
  7. func GrafanaProvData() {
  8. data := `apiVersion: 1
  9. deleteDatasources:
  10. - name: Clickhouse
  11. orgId: 1
  12. datasources:
  13. - name: Clickhouse
  14. type: vertamedia-clickhouse-datasource
  15. access: proxy
  16. url: http://localhost:8123
  17. basicAuth: 1
  18. basicAuthUser: default
  19. basicAuthPassword: $password
  20. isDefault: false
  21. defaultDatabase:`
  22. err := ioutil.WriteFile("/etc/grafana/provisioning/datasources/datasource.yml", []byte(data), os.ModePerm)
  23. if err != nil {
  24. fmt.Println("Error:", err)
  25. return
  26. }
  27. fmt.Println("File written successfully")
  28. }
  29. func main() {
  30. GrafanaProvData()
  31. }

这段代码将数据写入到/etc/grafana/provisioning/datasources/datasource.yml文件中。你可以根据需要修改数据内容。

英文:

Is it possible to do something in Golang similar to the below in bash

  1. GrafanaProvData()
  2. {
  3. cat > /etc/grafana/provisioning/datasources/datasource.yml << EOF
  4. apiVersion: 1
  5. deleteDatasources:
  6. - name: Clickhouse
  7. orgId: 1
  8. datasources:
  9. - name: Clickhouse
  10. type: vertamedia-clickhouse-datasource
  11. access: proxy
  12. url: http://localhost:8123
  13. basicAuth: 1
  14. basicAuthUser: default
  15. basicAuthPassword: $password
  16. isDefault: false
  17. defaultDatabase:
  18. EOF
  19. }

i have tried command.exec but its not exactly what is needed

答案1

得分: 3

这个shell脚本的作用是使用所谓的“here document”(在链接中有详细介绍)来将shell的标准输入管道传递给命令cat,而cat命令的标准输出则被重定向到一个文件中。通过使用>进行重定向,文件要么被创建,要么被截断。

正如一些人评论的那样,在Go语言中,这基本上等同于将一个字符串字面量写入文本文件。

但请注意,有一个细节:当shell处理here-document时,它会在实际使用之前对文档的文本进行各种扩展,包括变量扩展,除非采取特殊的措施来阻止扩展。因此,在你的示例中,变量“password”的内容将替换$password的位置。

考虑到这一点,你可以这样做:

  1. const tmpl = `apiVersion: 1
  2. deleteDatasources:
  3. - name: Clickhouse
  4. orgId: 1
  5. datasources:
  6. - name: Clickhouse
  7. type: vertamedia-clickhouse-datasource
  8. access: proxy
  9. url: http://localhost:8123
  10. basicAuth: 1
  11. basicAuthUser: default
  12. basicAuthPassword: %s
  13. isDefault: false
  14. defaultDatabase:
  15. `
  16. fd, err := os.OpenFile("/etc/grafana/provisioning/datasources/datasource.yml",
  17. os.O_WRONLY|os.O_TRUNC, 0666)
  18. if err != nil {
  19. log.Fatal(err)
  20. }
  21. defer fd.Close()
  22. _, err := fmt.Fprintf(tmpl, password)
  23. if err != nil {
  24. log.Fatal(err)
  25. }

另外,还有两点需要考虑:

  • 如果模板变得更加复杂,使用fmt.*f系列函数来完成任务会变得越来越困难;这时你可能需要查看text/template标准包。
  • 如果你需要对YAML模板进行更高级的处理,可能更方便的做法是停止使用格式无关的方法,而是使用Go中的任何YAML处理包,比如古老的gopkg.in/yaml.v3,首先填充一个自定义复合类型的变量,然后将其编组为一个YAML格式的文档。这样可以避免某些奇怪的错误(考虑一下:如果你的密码包含会被YAML解析器解释为合法YAML语法的字符,会发生什么?一个“真正”的YAML编组器会转义任何可能被错误解释的内容,而任何“模板化”方法则不会)。

还有一个提示:当要写入的文件很大(比如超过10k)时,将bufio.Writer放在fmt.Fprint*函数和打开的文件之间可能是值得的,以优化文件系统访问。

英文:

What happens in this shell script is the usage of the so-called "here document" which is piped by the shell to the standard input of the command cat whose standard output is directed to a file, which — due to the redirection set up using > — will be either created or truncated.

As commented by some folks, in Go, that would mostly amount to writing a string literal to a text file.

But note that there is a twist: when here-documents are processed by the shell it performs various expansions — including variable expansion — in the document's text before actually using it — unless special action is taken to prevent that.
So, in your example, the contents of the variable "password" would be substituted in place of $password.

With this in mind, what you'd do is something like

  1. const tmpl = `apiVersion: 1
  2. deleteDatasources:
  3. - name: Clickhouse
  4. orgId: 1
  5. datasources:
  6. - name: Clickhouse
  7. type: vertamedia-clickhouse-datasource
  8. access: proxy
  9. url: http://localhost:8123
  10. basicAuth: 1
  11. basicAuthUser: default
  12. basicAuthPassword: %s
  13. isDefault: false
  14. defaultDatabase:
  15. `
  16. fd, err := os.OpenFile("/etc/grafana/provisioning/datasources/datasource.yml",
  17. os.O_WRONLY|os.O_TRUNC, 0666)
  18. if err != nil {
  19. log.Fatal(err)
  20. }
  21. defer fd.Close()
  22. _, err := fmt.Fprintf(tmpl, password)
  23. if err != nil {
  24. log.Fatal(err)
  25. }

While we are at it, consider two more things:

  • If/when the template becomes more complicated, it would become progressively harder to use the functions of the fmt.*f family to cut the task; you then might want to look at the text/template standard package.
  • If/when you would need even more advanced processing of your YAML templates, it might be more convenient to stop being format-agnostic and use any YAML-handling package for Go — such as the venerable gopkg.in/yaml.v3 to first fill a variable of some custom compound type and then marshal it as a YAML-formatted document.
    This might help prevent a certain class of weird erorrs (consider: what happens if your password contains letters which will be interpreted by a YAML parser as a proper YAML syntax? A "real" YAML marshaler would escape anything which would be improperly interpreted — while any "templating" approach would not.)

One more hint: when the file to be written is big (like, over 10k), it might be worthwhile to stick a bufio.Writer between the fmt.Fprint* functions and the opened file—to optimize filesystem access.

huangapple
  • 本文由 发表于 2021年11月22日 18:38:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/70064438.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定