Go embed 简明教程 | 鸟窝
鸟窝
大道至简 Simplicity is the ultimate form of sophistication
Go 汇编示例 Go Web 开发示例 Go 数据库开发教程
by smallnest
Go embed 简明教程
Go 编译的程序非常适合部署,如果没有通过 CGO 引用其它的库的话,我们一般编译出来的可执行二进制文件都是单个的文件,非常适合复制和部署。在实际使用中,除了二进制文件,可能还需要一些配置文件,或者静态文件,比如 html 模板、静态的图片、CSS、javascript 等文件,如何这些文件也能打进到二进制文件中,那就太美妙,我们只需复制、按照单个的可执行文件即可。
一些开源的项目很久以前就开始做这方面的工作,比如gobuffalo/packr、markbates/pkger、rakyll/statik、knadh/stuffbin等等,但是不管怎么说这些都是第三方提供的功能,如果 Go 官方能内建支持就好了。2019 末一个提案被提出issue#35950, 期望 Go 官方编译器支持嵌入静态文件。后来 Russ Cox 专门写了一个设计文档Go command support for embedded static assets, 并最终实现了它。
Go 1.16 中包含了 go embed 的功能,而且 Go1.16 基本在一个月左右的时间就会发布了,到时候你可以尝试使用它,如果你等不及了,你也可以下载 Go 1.16beta1 尝鲜。
本文将通过例子,详细介绍 go embed 的各个功能。
嵌入
- 对于单个的文件,支持嵌入为字符串和 byte slice
 - 对于多个文件和文件夹,支持嵌入为新的文件系统 FS
 - 比如导入 “embed” 包,即使无显式的使用
 go:embed指令用来嵌入,必须紧跟着嵌入后的变量名- 只支持嵌入为 string, byte slice 和 embed.FS 三种类型,这三种类型的别名 (alias) 和命名类型 (如 type S string) 都不可以
 
嵌入为字符串
比如当前文件下有个 hello.txt 的文件,文件内容为hello,world!。通过go:embed指令,在编译后下面程序中的 s 变量的值就变为了hello,world!。
package mainimport (_ "embed""fmt")//go:embed hello.txtvar s stringfunc main() {fmt.Println(s)}
嵌入为 byte slice
你还可以把单个文件的内容嵌入为 slice of byte,也就是一个字节数组。
package mainimport (_ "embed""fmt")//go:embed hello.txtvar b []bytefunc main() {fmt.Println(b)}
嵌入为 fs.FS
甚至你可以嵌入为一个文件系统,这在嵌入多个文件的时候非常有用。
比如嵌入一个文件:
package mainimport ("embed""fmt")//go:embed hello.txtvar f embed.FSfunc main() {data, _ := f.ReadFile("hello.txt")fmt.Println(string(data))}
嵌入本地的另外一个文件 hello2.txt, 支持同一个变量上多个go:embed指令 (嵌入为 string 或者 byte slice 是不能有多个go:embed指令的):
package mainimport ("embed""fmt")//go:embed hello.txt//go:embed hello2.txtvar f embed.FSfunc main() {data, _ := f.ReadFile("hello.txt")fmt.Println(string(data))data, _ = f.ReadFile("hello2.txt")fmt.Println(string(data))}
当前重复的go:embed指令嵌入为 embed.FS 是支持的,相当于一个:
package mainimport ("embed""fmt")//go:embed hello.txt//go:embed hello.txtvar f embed.FSfunc main() {data, _ := f.ReadFile("hello.txt")fmt.Println(string(data))}
还可以嵌入子文件夹下的文件:
package mainimport ("embed""fmt")//go:embed p/hello.txt//go:embed p/hello2.txtvar f embed.FSfunc main() {data, _ := f.ReadFile("p/hello.txt")fmt.Println(string(data))data, _ = f.ReadFile("p/hello2.txt")fmt.Println(string(data))}
还可以支持模式匹配的方式嵌入,下面的章节专门介绍。
同一个文件嵌入为多个变量
比如下面的例子,s 和 s2 变量都嵌入 hello.txt 的文件。
package mainimport (_ "embed""fmt")//go:embed hello.txtvar s string//go:embed hello.txtvar s2 stringfunc main() {fmt.Println(s)fmt.Println(s2)}
exported/unexported 的变量都支持
Go 可以将文件可以嵌入为 exported 的变量,也可以嵌入为 unexported 的变量。
package mainimport (_ "embed""fmt")//go:embed hello.txtvar s string//go:embed hello2.txtvar S stringfunc main() {fmt.Println(s)fmt.Println(S)}
package 级别的变量和局部变量都支持
前面的例子都是 package 一级的的变量,即使是函数内的局部变量,也都支持嵌入:
package mainimport (_ "embed""fmt")func main() {//go:embed hello.txtvar s string//go:embed hello.txtvar s2 stringfmt.Println(s, s2)}
局部变量 s 的值在编译时就已经嵌入了,而且虽然 s 和 s2 嵌入同一个文件,但是它们的值在编译的时候会使用初始化字段中的不同的值:
0x0021 00033 (/Users/....../main.go:10) MOVQ "".embed.1(SB), AX0x0028 00040 (/Users/....../main.go:10) MOVQ "".embed.1+8(SB), CX0x002f 00047 (/Users/....../main.go:13) MOVQ "".embed.2(SB), DX0x0036 00054 (/Users/....../main.go:13) MOVQ DX, "".s2.ptr+72(SP)0x003b 00059 (/Users/....../main.go:13) MOVQ "".embed.2+8(SB), BX......"".embed.1 SDATA size=160x0000 00 00 00 00 00 00 00 00 0d 00 00 00 00 00 00 00 ................rel 0+8 t=1 go.string."hello, world!"+0"".embed.2 SDATA size=160x0000 00 00 00 00 00 00 00 00 0d 00 00 00 00 00 00 00 ................rel 0+8 t=1 go.string."hello, world!"+0
注意 s 和 s2 的变量的值是在编译期就确定了,即使在运行时你更改了 hello.txt 的文件,甚至把 hello.txt 都删除了也不会改变和影响 s 和 s2 的值。
只读
嵌入的内容是只读的。也就是在编译期嵌入文件的内容是什么,那么在运行时的内容也就是什么。
FS 文件系统值提供了打开和读取的方法,并没有 write 的方法,也就是说 FS 实例是线程安全的,多个 goroutine 可以并发使用。
type FSfunc (f FS) Open(name string) (fs.File, error)func (f FS) ReadDir(name string) ([]fs.DirEntry, error)func (f FS) ReadFile(name string) ([]byte, error)
go:embed 指令
go:embed 指令支持嵌入多个文件
package mainimport ("embed""fmt")//go:embed hello.txt hello2.txtvar f embed.FSfunc main() {data, _ := f.ReadFile("hello.txt")fmt.Println(string(data))data, _ = f.ReadFile("hello2.txt")fmt.Println(string(data))}
当然你也可以像前面的例子一样写成多行go:embed:
package mainimport ("embed""fmt")//go:embed hello.txt//go:embed hello2.txtvar f embed.FSfunc main() {data, _ := f.ReadFile("hello.txt")fmt.Println(string(data))data, _ = f.ReadFile("hello2.txt")fmt.Println(string(data))}
支持文件夹
文件夹分隔符采用正斜杠/, 即使是 windows 系统也采用这个模式。
package mainimport ("embed""fmt")//go:embed pvar f embed.FSfunc main() {data, _ := f.ReadFile("p/hello.txt")fmt.Println(string(data))data, _ = f.ReadFile("p/hello2.txt")fmt.Println(string(data))}
使用的是相对路径
相对路径的根路径是 go 源文件所在的文件夹。
支持使用双引号"或者反引号的方式应用到嵌入的文件名或者文件夹名或者模式名上,这对名称中带空格或者特殊字符的文件文件夹有用。
package mainimport ("embed""fmt")//go:embed "he llo.txt" `hello-2.txt`var f embed.FSfunc main() {data, _ := f.ReadFile("he llo.txt")fmt.Println(string(data))}
匹配模式
go:embed指令中可以只写文件夹名,此文件夹中除了.和_开头的文件和文件夹都会被嵌入,并且子文件夹也会被递归的嵌入,形成一个此文件夹的文件系统。
如果想嵌入.和_开头的文件和文件夹, 比如 p 文件夹下的. hello.txt 文件,那么就需要使用*,比如go:embed p/*。
*不具有递归性,所以子文件夹下的.和_不会被嵌入,除非你在专门使用子文件夹的*进行嵌入:
package mainimport ("embed""fmt")//go:embed p/*var f embed.FSfunc main() {data, _ := f.ReadFile("p/.hello.txt")fmt.Println(string(data))data, _ = f.ReadFile("p/q/.hi.txt") // 没有嵌入 p/q/.hi.txtfmt.Println(string(data))}
嵌入和嵌入模式不支持绝对路径、不支持路径中包含.和.., 如果想嵌入 go 源文件所在的路径,使用*:
package mainimport ("embed""fmt")//go:embed *var f embed.FSfunc main() {data, _ := f.ReadFile("hello.txt")fmt.Println(string(data))data, _ = f.ReadFile(".hello.txt")fmt.Println(string(data))}
文件系统
embed.FS实现了 io/fs.FS接口,它可以打开一个文件,返回fs.File:
package mainimport ("embed""fmt")//go:embed *var f embed.FSfunc main() {helloFile, _ := f.Open("hello.txt")stat, _ := helloFile.Stat()fmt.Println(stat.Name(), stat.Size())}
它还提供了 ReadFileh 和 ReadDir 功能,遍历一个文件下的文件和文件夹信息:
package mainimport ("embed""fmt")//go:embed *var f embed.FSfunc main() {dirEntries, _ := f.ReadDir("p")for _, de := range dirEntries {fmt.Println(de.Name(), de.IsDir())}}
因为它实现了io/fs.FS接口,所以可以返回它的子文件夹作为新的文件系统:
package mainimport ("embed""fmt""io/fs""io/ioutil")//go:embed *var f embed.FSfunc main() {ps, _ := fs.Sub(f, "p")hi, _ := ps.Open("q/hi.txt")data, _ := ioutil.ReadAll(hi)fmt.Println(string(data))}
应用
net/http
先前,我们提供一个静态文件的服务时,使用:
http.Handle("/", http.FileServer(http.Dir("/tmp")))
现在,io/fs.FS文件系统也可以转换成 http.FileServer 的参数了:
type FileSystemfunc FS(fsys fs.FS) FileSystemtype Handlerfunc FileServer(root FileSystem) Handler
所以,嵌入文件可以使用下面的方式:
http.Handle("/", http.FileServer(http.FS(fsys)))
text/template 和 html/template.
同样的, template 也可以从嵌入的文件系统中解析模板:
func ParseFS(fsys fs.FS, patterns ...string) (*Template, error)func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error)
3 条评论
未登录用户
支持 Markdown 语法预览使用 GitHub 登录

RitterHou发表于 3 个月前
哇!真棒👍!go 真是越来越好用了!

yqchilde发表于 3 个月前
充满活力的 Go!

BaBahsq发表于 5 天前
经过测试, 很多地方貌似不同了, //go:embed ..\config.toml 这种写法才是对的
[Newer
利好!极大可能在 go 1.17 中就能尝试泛型
](/2021/02/20/merge-dev-typeparams-to-master-during-Go-1-17/)[Older
如何写出内存泄露的程序?
](/2021/01/01/how-to-write-memory-leak-bugs-in-go/)
原创图书
分类
- Android12
 - C++1
 - DOTNET1
 - Docker5
 - Go163
 - Java64
 - Linux7
 - Rust12
 - Scala18
 - 分享1
 - 前端开发18
 - 区块链8
 - 大数据60
 - 工具27
 - 数据库3
 - 架构26
 - 算法4
 - 管理2
 - 网络编程13
 - 读书笔记2
 - 运维2
 - 高并发编程20
 
标签云
AndroidApacheBenchBowerC#CDNCQRSCRCCSSCompletableFutureComsatCuratorDSLDisruptorDockerEmberFastJsonFiberGAEGCGnuplotGoGradleGruntGulpHadoopHazelcastIPFSIgniteJVMJavaKafkaLambdaLinuxLongAdderMathJaxMavenMemcachedMetricsMongoNetty
归档
- April 20211
 - March 20212
 - February 20211
 - January 20212
 - December 20203
 - November 20202
 - September 20201
 - August 20201
 - July 20201
 - June 20202
 - May 20204
 - April 20201
 - March 20203
 - February 20202
 - January 20205
 - December 20196
 - November 20192
 - October 20196
 - September 20197
 - August 20197
 - July 20197
 - June 20191
 - May 20192
 - April 20193
 - March 20191
 - February 20196
 - January 20195
 - December 20182
 - November 20184
 - October 20182
 - September 20186
 - August 20185
 - July 20183
 - June 20183
 - May 20182
 - April 20181
 - March 20186
 - February 20184
 - January 20183
 - December 20177
 - November 20174
 - October 20176
 - September 20174
 - August 20174
 - July 20174
 - June 20177
 - May 20174
 - April 20177
 - March 20176
 - February 20173
 - January 20173
 - December 20165
 - November 20167
 - October 20166
 - September 20165
 - August 20164
 - July 201612
 - June 201614
 - May 20166
 - April 201614
 - March 20167
 - February 20168
 - January 20161
 - December 20153
 - November 201510
 - October 20159
 - September 201512
 - August 201512
 - July 201512
 - June 20158
 - May 20157
 - April 201515
 - March 201510
 - February 20154
 - January 201512
 - December 201428
 - November 201412
 - October 201410
 - September 201428
 - August 201419
 - July 20141
 
近期文章
友情链接
© 2021 smallnest
Powered by Hexo
首页 归档 github 网站群 Go 汇编示例 Go Web 开发示例 Go 数据库开发教程 RPCX 官网 RPC 开发指南 Scala 集合技术手册 关于



