不打印的情况下格式化Go字符串?

557
有没有一种简单的方式在Go中格式化字符串而不打印该字符串?
我可以这样做:
bar := "bar"
fmt.Printf("foo: %s", bar)

但我希望返回格式化后的字符串而不是打印出来,这样我就可以进一步操作它。

我也可以像这样做:

s := "foo: " + bar

但是当格式字符串变得复杂时,阅读起来会变得困难,并且如果其中一个或多个部分不是字符串而需要先转换时,操作就会很繁琐,例如:

i := 25
s := "foo: " + strconv.Itoa(i)

有更简单的方法吗?

8个回答

687
Sprintf 是你要找的东西。 示例
fmt.Sprintf("foo: %s", bar)

您还可以在“Go语言之旅”中的错误示例中看到它的使用。

return fmt.Sprintf("at %v, %s", e.When, e.What)

7
信件中的%后面的字母有关系吗?它可以是%y和%q吗?或者是%y和%y? - Filip Bartuzi
28
信件很重要,它被称为动词,基本上它让Sprintf知道变量的类型,这样如果它接收到65并且动词是%d,它将打印数字65,但如果动词是%c,则会打印字符'A'。请参见:https://golang.org/pkg/fmt/#hdr-Printing - redsalt
10
为什么它被称为Sprintf?S代表字符串,f代表格式吗?如果该函数没有输出到屏幕,那么print为何成为函数名称的一部分是很奇怪的。这让我困惑了一段时间... - jcollum
1
@jcollum 它执行 C 标准库的 sprintf 函数的功能(就像 fmt.Fprintf 执行 C 的 fprintf 函数的功能一样),因此它被命名为 C。C 标准库函数 sprintf 中的 S 代表什么?我不确定。可能是 "string",因为结果输出到字符串缓冲区而不是文件缓冲区或 stdout。 - tedtanner
1
在 C 语言中,“sprintf”已经是一个愚蠢的名称,因为它没有打印任何东西。我不明白为什么一种想要改进的语言会实现旧的误导怪异之处。 - Blcknx

263

1. 简单字符串

对于“简单”字符串(通常适合一行内的字符串),最简单的解决方案是使用fmt.Sprintf()和相关函数(fmt.Sprint()fmt.Sprintln())。这些函数类似于没有首字母S的函数,但这些Sxxx()变体将结果作为string返回,而不是将它们打印到标准输出。

例如:

s := fmt.Sprintf("Hi, my name is %s and I'm %d years old.", "Bob", 23)

变量s将被初始化为以下值:
Hi, my name is Bob and I'm 23 years old.

提示: 如果您只想连接不同类型的值,您可能不需要自动使用 Sprintf() (需要格式字符串),因为Sprint() 正好可以做到这一点。请参考以下示例:

i := 23
s := fmt.Sprint("[age:", i, "]") // s will be "[age:23]"

如果您只需要连接string,您也可以使用strings.Join(),其中您可以指定自定义分隔符string(放置在要连接的字符串之间)。

请在Go Playground上尝试这些内容。

2. 复杂字符串(文档)

如果您要创建的字符串更复杂(例如多行电子邮件消息),则fmt.Sprintf()变得不太可读且效率较低(特别是如果您必须这样做很多次)。

为此,标准库提供了text/templatehtml/template包。这些包实现了用于生成文本输出的数据驱动模板。html/template用于生成针对代码注入安全的HTML输出。它提供与包text/template相同的接口,并且应该在输出为HTML时替代使用text/template

使用template包基本上要求您提供静态模板,形式为string值(可以来自文件,因此您只提供文件名),其中可以包含静态文本和操作,当引擎处理模板并生成输出时,这些操作将被处理和执行。

您可以提供参数,这些参数包含/替换在静态模板中,并且可以控制输出生成过程。此类参数的典型形式是嵌套的structmap值。

示例:

例如,假设您想要生成如下所示的电子邮件消息:

Hi [name]!

Your account is ready, your user name is: [user-name]

You have the following roles assigned:
[role#1], [role#2], ... [role#n]

为了生成像这样的电子邮件消息正文,您可以使用以下静态模板:
const emailTmpl = `Hi {{.Name}}!

Your account is ready, your user name is: {{.UserName}}

You have the following roles assigned:
{{range $i, $r := .Roles}}{{if $i}}, {{end}}{{.}}{{end}}
`

并提供像这样的数据来执行它:

data := map[string]interface{}{
    "Name":     "Bob",
    "UserName": "bob92",
    "Roles":    []string{"dbteam", "uiteam", "tester"},
}

通常,模板的输出被写入到一个io.Writer中,如果你想要将结果作为string返回,则需要创建并写入bytes.Buffer(其实现了io.Writer)。执行模板并将结果作为string返回:

t := template.Must(template.New("email").Parse(emailTmpl))
buf := &bytes.Buffer{}
if err := t.Execute(buf, data); err != nil {
    panic(err)
}
s := buf.String()

这将产生预期的输出结果:
Hi Bob!

Your account is ready, your user name is: bob92

You have the following roles assigned:
dbteam, uiteam, tester

Go Playground上试用一下。

另外需要注意的是,自Go 1.10以来,一个更新、更快、更专业的替代品“strings.Builder”可供使用,它比bytes.Buffer更好。使用方法非常相似:

builder := &strings.Builder{}
if err := t.Execute(builder, data); err != nil {
    panic(err)
}
s := builder.String()

尝试在Go Playground上运行此代码。
注意:如果您将os.Stdout作为目标(它也实现了io.Writer),则还可以显示模板执行的结果。
t := template.Must(template.New("email").Parse(emailTmpl))
if err := t.Execute(os.Stdout, data); err != nil {
    panic(err)
}

这将直接将结果写入os.Stdout。在Go Playground上尝试一下。


7

尝试使用Sprintf()函数,它不会打印输出结果,但可以将其保存以备将来使用。 看这个示例:

package main

import "fmt"

func main() {
    
    address := "NYC"

    fmt.Sprintf("I live in %v", address)

}

当你运行这段代码时,它不会输出任何内容。但是一旦将Sprintf()分配给一个单独的变量,它就可以用于未来的目的。
package main

import "fmt"

func main() {
    
    address := "NYC"

    fmt.Sprintf("I live in %v", address)

    var city = fmt.Sprintf("lives in %v", address)
    fmt.Println("Michael",city)

}

5

fmt.Sprintf 函数返回一个字符串,您可以使用与 fmt.Printf 相同的方式格式化该字符串。


3

我已经为字符串格式化创建了一个go项目(它允许像C#或Python一样格式化字符串),通过性能测试,它比fmt.Sprintf更快,你可以在这里找到它:https://github.com/Wissance/stringFormatter

它的工作方式如下:


func TestStrFormat(t *testing.T) {
    strFormatResult, err := Format("Hello i am {0}, my age is {1} and i am waiting for {2}, because i am {0}!",
                              "Michael Ushakov (Evillord666)", "34", "\"Great Success\"")
    assert.Nil(t, err)
    assert.Equal(t, "Hello i am Michael Ushakov (Evillord666), my age is 34 and i am waiting for \"Great Success\", because i am Michael Ushakov (Evillord666)!", strFormatResult)

    strFormatResult, err = Format("We are wondering if these values would be replaced : {5}, {4}, {0}", "one", "two", "three")
    assert.Nil(t, err)
    assert.Equal(t, "We are wondering if these values would be replaced : {5}, {4}, one", strFormatResult)

    strFormatResult, err = Format("No args ... : {0}, {1}, {2}")
    assert.Nil(t, err)
    assert.Equal(t, "No args ... : {0}, {1}, {2}", strFormatResult)
}

func TestStrFormatComplex(t *testing.T) {
    strFormatResult, err := FormatComplex("Hello {user} what are you doing here {app} ?", map[string]string{"user":"vpupkin", "app":"mn_console"})
    assert.Nil(t, err)
    assert.Equal(t, "Hello vpupkin what are you doing here mn_console ?", strFormatResult)
}


2

在您的情况下,您需要使用Sprintf()进行格式化字符串。

func Sprintf(format string, a ...interface{}) string

Sprintf根据格式说明符进行格式化,并返回结果字符串。

s := fmt.Sprintf("早上好,我是%s,已经在这里生活了%d年。", "约翰", 20)

你的输出将是:

早上好,我是约翰,已经在这里生活了20年。


0

我来到这个页面是特别寻找一种格式化错误字符串的方法。所以如果有人需要相同的帮助,你可以使用fmt.Errorf()函数。

该方法的签名为func Errorf(format string, a ...interface{}) error。 它将格式化后的字符串作为值返回,该值满足error接口。

您可以在文档中查找更多详细信息 - https://golang.org/pkg/fmt/#Errorf


-2

不必使用template.New,你可以直接使用内置的newtemplate.Template

package main

import (
   "strings"
   "text/template"
)

func format(s string, v interface{}) string {
   t, b := new(template.Template), new(strings.Builder)
   template.Must(t.Parse(s)).Execute(b, v)
   return b.String()
}

func main() {
   bar := "bar"
   println(format("foo: {{.}}", bar))
   i := 25
   println(format("foo: {{.}}", i))
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接