Golang 1.16 新特性:embed使用教程
(给Go开发大全
加星标)
【导读】golang 1.16版本已经发布,关于新版本的新功能embed如何使用,本文是一篇go官方推荐教程翻译。
Go1.16版本引入了一个新的//go:embed
命令,这个命令可以在go应用程序里引入二进制文件和目录。我写了一些例子放在代码仓库里(https://github.com/carlmjohnson/exembed/),验证了//go:embed
的功能,本文的例子代码都在这个仓库里。
嵌入的这个基本概念是通过在代码里添加一个特殊的注释实现的,Go会根据这个注释知道要引入哪个或哪几个文件。注释的格式是:
//go:embed FILENAME(S)
FILENAME可以是string类型也可以是[]byte类型,取决于你引入的是单个文件、还是embed.FS
类型的一组文件。go:embed
命令可以识别Go的文件格式,比如files/*.html
这种文件格式也可以识别到(但要注意不要写成**/*.html
这种递归的匹配规则)。
文件格式https://pkg.go.dev/path#Match
可以看下官方文档的说明。https://golang.org/pkg/embed/
接下来我们看一些例子了解//go:embed
。
版本信息
//go:embed
让我们很容易就能记录一个version.txt文件的版本信息:
package main
import (
_ "embed"
"fmt"
"strings"
)
var (
Version string = strings.TrimSpace(version)
//go:embed version.txt
version string
)
func main() {
fmt.Printf("Version %q\n", Version)
}
接下来是一个更复杂的例子。我们还能在代码里按照条件保留版本信息,比如编译时是否传递了某个tag来记录版本:
// version_dev.go
// +build !prod
package main
var version string = "dev"
// version_prod.go
// +build prod
package main
import (
_ "embed"
)
//go:embed version.txt
var version string
执行命令运行、验证结果:
$ go run .
Version "dev"
$ go run -tags prod .
Version "0.0.1"
Quine
quine是一个程序,它负责打印源代码,我们看个例子:
package main
import (
_ "embed"
"fmt"
)
//go:embed quine.go
var src string
func main() {
fmt.Print(src)
}
运行程序的时候自动就打印了版本信息。
嵌入复杂结构体
如果有一些需要预计算的复杂信息,可以把这些预计算操作保存到go项目里。需要做的就是把数据存储成go可以读取的格式、并在程序启动时加载这些序列化的数据即可:
package main
import (
"bytes"
_ "embed"
"encoding/gob"
"fmt"
)
var (
// File value.gob contains some complicated data
// which we have precomputed and saved.
//go:embed value.gob
b []byte
s = func() (s struct {
Number float64
Weather string
Alphabet []string
}) {
dec := gob.NewDecoder(bytes.NewReader(b))
if err := dec.Decode(&s); err != nil {
panic(err)
}
return
}()
)
func main() {
fmt.Printf("s: %#v\n", s)
}
网页文件
这部分是//go:embed
主要应用场景。有了embed就可以在一个可执行文件里包含所有静态文件和模板了。还可根据命令行参数让程序去读磁盘上的文件还是去读打包好的、嵌入的文件:
package main
import (
"embed"
"io/fs"
"log"
"net/http"
"os"
)
func main() {
useOS := len(os.Args) > 1 && os.Args[1] == "live"
http.Handle("/", http.FileServer(getFileSystem(useOS)))
http.ListenAndServe(":8888", nil)
}
//go:embed static
var embededFiles embed.FS
func getFileSystem(useOS bool) http.FileSystem {
if useOS {
log.Print("using live mode")
return http.FS(os.DirFS("static"))
}
log.Print("using embed mode")
fsys, err := fs.Sub(embededFiles, "static")
if err != nil {
panic(err)
}
return http.FS(fsys)
}
需要注意,embed.FS
的路径前缀需要用fs.Sub
去掉,这样才能让os.DirFS
成功匹配到。
下面是另一个例子。具体展示了嵌入模板的用法:
package main
import (
"embed"
"os"
"text/template"
)
//go:embed *.tmpl
var tpls embed.FS
func main() {
name := "en.tmpl"
if len(os.Args) > 1 {
name = os.Args[1] + ".tmpl"
}
arg := "World"
if len(os.Args) > 2 {
arg = os.Args[2]
}
t, err := template.ParseFS(tpls, "*")
if err != nil {
panic(err)
}
if err = t.ExecuteTemplate(os.Stdout, name, arg); err != nil {
panic(err)
}
}
英文模板en.tmpl
的内容是Hello {{ . }}, how are you today?
、日文模板jp.tmpl
内容是こんにちは{{ . }}。お元気ですか。
,运行程序:
默认(英文):
$ go run ./main.go
Hello World, how are you today?
指定用jp模板:
$ go run ./main.go jp ワールド
こんにちはワールド。お元気ですか。
一些坑
用//go:embed
需要注意避开一些坑。首先,使用embed 就要在文件里引入embed
包,否则会报错,下面这段代码就不能运行:
package main
import (
"fmt"
)
//go:embed file.txt
var s string
func main() {
fmt.Print(s)
}
具体报错如下:
$ go run missing-embed.go
# command-line-arguments
./missing-embed.go:8:3: //go:embed only allowed in Go files that import "embed"
通常情况下go编程中是不建议在代码里引入不调用的包的,但是如果引入embed但是又不会在代码里有显示调用,因为前面也讲了这只是个在注释里起作用的东西,这时候就必须用import _ "embed"
这样的写法才能正常引入、但是看起来就像是embed没有被调用一样,所以更要注意。
另一个坑是你只能在包的那层用//go:embed
注释,不能写进函数、方法里面,下面是一个错误写法的例子:
package main
import (
_ "embed"
"fmt"
)
func main() {
//go:embed file.txt
var s string
fmt.Print(s)
}
运行程序报错:
$ go run bad-level.go
# command-line-arguments
./bad-level.go:9:4: go:embed cannot apply to var inside func
第三个坑是引入一个目录时,会忽略掉文件名里.
和_
开头的文件。但是如果写了通配符、正则匹配,比如用dir/*
这种写法,就会包含.
和_
开头的文件了。一定要注意mac os系统的.DS_Store
文件可能会被误引用,这对于web服务来说可能会有些安全问题。出于安全考虑,go也不允许用embed功能时引用连接符和查找路径。
embed适用于任何需要的应用,比如你想要让程序命令行应用可以读取README和licence信息,或是把查询数据库的.sql
文件都作为嵌入文件存储到应用里,甚至还可以写一个覆盖式FS,以将内置的embed.FS
与用户提供的覆盖文件结合起来等等。前面提到的这些都是一些浅显的大概想法,我认为随着时间推移会有更多人提出更好的实践方法的。
go 1.16已经于2021年2月16日发布了,还没升级版本的可以试试升级了。
- EOF -
Go 开发大全
参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。
关注后获取
回复 Go 获取6万star的Go资源库
分享、点赞和在看
支持我们分享更多好文章,谢谢!