转载地址: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 main

import (

_ "embed"

"fmt"

)

//go:embed hello.txt

var s string

func main() {

fmt.Println(s)

}

嵌入为byte slice

你还可以把单个文件的内容嵌入为slice of byte,也就是一个字节数组。


1

2

3

4

5

6

7

8

9

10

11

12

13


package main

import (

_ "embed"

"fmt"

)

//go:embed hello.txt

var b []byte

func main() {

fmt.Println(b)

}

嵌入为fs.FS

甚至你可以嵌入为一个文件系统,这在嵌入多个文件的时候非常有用。
比如嵌入一个文件:


1

2

3

4

5

6

7

8

9

10

11

12

13

14


package main

import (

"embed"

"fmt"

)

//go:embed hello.txt

var f embed.FS

func main() {

data, _ := f.ReadFile("hello.txt")

fmt.Println(string(data))

}

嵌入本地的另外一个文件hello2.txt, 支持同一个变量上多个go:embed指令(嵌入为string或者byte slice是不能有多个go:embed指令的):


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


package main

import (

"embed"

"fmt"

)

//go:embed hello.txt

//go:embed hello2.txt

var f embed.FS

func main() {

data, _ := f.ReadFile("hello.txt")

fmt.Println(string(data))

data, _ = f.ReadFile("hello2.txt")

fmt.Println(string(data))

}

当前重复的go:embed指令嵌入为embed.FS是支持的,相当于一个:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15


package main

import (

"embed"

"fmt"

)

//go:embed hello.txt

//go:embed hello.txt

var f embed.FS

func main() {

data, _ := f.ReadFile("hello.txt")

fmt.Println(string(data))

}

还可以嵌入子文件夹下的文件:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


package main

import (

"embed"

"fmt"

)

//go:embed p/hello.txt

//go:embed p/hello2.txt

var f embed.FS

func 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的文件。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18


package main

import (

_ "embed"

"fmt"

)

//go:embed hello.txt

var s string

//go:embed hello.txt

var s2 string

func main() {

fmt.Println(s)

fmt.Println(s2)

}

exported/unexported的变量都支持

Go可以将文件可以嵌入为exported的变量,也可以嵌入为unexported的变量。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18


package main

import (

_ "embed"

"fmt"

)

//go:embed hello.txt

var s string

//go:embed hello2.txt

var S string

func main() {

fmt.Println(s)

fmt.Println(S)

}

package级别的变量和局部变量都支持

前面的例子都是package一级的的变量,即使是函数内的局部变量,也都支持嵌入:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16


package main

import (

_ "embed"

"fmt"

)

func main() {

//go:embed hello.txt

var s string

//go:embed hello.txt

var s2 string

fmt.Println(s, s2)

}

局部变量s的值在编译时就已经嵌入了,而且虽然s和s2嵌入同一个文件,但是它们的值在编译的时候会使用初始化字段中的不同的值:


1

2

3

4

5

6

7

8

9

10

11

12

13

14


0x0021 00033 (/Users/....../main.go:10) MOVQ "".embed.1(SB), AX

0x0028 00040 (/Users/....../main.go:10) MOVQ "".embed.1+8(SB), CX

0x002f 00047 (/Users/....../main.go:13) MOVQ "".embed.2(SB), DX

0x0036 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=16

0x0000 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=16

0x0000 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可以并发使用。


1

2

3

4


type FS

func (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指令支持嵌入多个文件


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


package main

import (

"embed"

"fmt"

)

//go:embed hello.txt hello2.txt

var f embed.FS

func main() {

data, _ := f.ReadFile("hello.txt")

fmt.Println(string(data))

data, _ = f.ReadFile("hello2.txt")

fmt.Println(string(data))

}

当然你也可以像前面的例子一样写成多行go:embed:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18


package main

import (

"embed"

"fmt"

)

//go:embed hello.txt

//go:embed hello2.txt

var f embed.FS

func main() {

data, _ := f.ReadFile("hello.txt")

fmt.Println(string(data))

data, _ = f.ReadFile("hello2.txt")

fmt.Println(string(data))

}

支持文件夹

文件夹分隔符采用正斜杠/,即使是windows系统也采用这个模式。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


package main

import (

"embed"

"fmt"

)

//go:embed p

var f embed.FS

func main() {

data, _ := f.ReadFile("p/hello.txt")

fmt.Println(string(data))

data, _ = f.ReadFile("p/hello2.txt")

fmt.Println(string(data))

}

使用的是相对路径

相对路径的根路径是go源文件所在的文件夹。

支持使用双引号"或者反引号的方式应用到嵌入的文件名或者文件夹名或者模式名上,这对名称中带空格或者特殊字符的文件文件夹有用。


1

2

3

4

5

6

7

8

9

10

11

12

13

14


package main

import (

"embed"

"fmt"

)

//go:embed "he llo.txt" `hello-2.txt`

var f embed.FS

func main() {

data, _ := f.ReadFile("he llo.txt")

fmt.Println(string(data))

}

匹配模式

go:embed指令中可以只写文件夹名,此文件夹中除了._开头的文件和文件夹都会被嵌入,并且子文件夹也会被递归的嵌入,形成一个此文件夹的文件系统。

如果想嵌入._开头的文件和文件夹, 比如p文件夹下的.hello.txt文件,那么就需要使用*,比如go:embed p/*

*不具有递归性,所以子文件夹下的._不会被嵌入,除非你在专门使用子文件夹的*进行嵌入:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


package main

import (

"embed"

"fmt"

)

//go:embed p/*

var f embed.FS

func main() {

data, _ := f.ReadFile("p/.hello.txt")

fmt.Println(string(data))

data, _ = f.ReadFile("p/q/.hi.txt") // 没有嵌入 p/q/.hi.txt

fmt.Println(string(data))

}

嵌入和嵌入模式不支持绝对路径、不支持路径中包含...,如果想嵌入go源文件所在的路径,使用*:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


package main

import (

"embed"

"fmt"

)

//go:embed *

var f embed.FS

func 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:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15


package main

import (

"embed"

"fmt"

)

//go:embed *

var f embed.FS

func main() {

helloFile, _ := f.Open("hello.txt")

stat, _ := helloFile.Stat()

fmt.Println(stat.Name(), stat.Size())

}

它还提供了ReadFileh和ReadDir功能,遍历一个文件下的文件和文件夹信息:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16


package main

import (

"embed"

"fmt"

)

//go:embed *

var f embed.FS

func main() {

dirEntries, _ := f.ReadDir("p")

for _, de := range dirEntries {

fmt.Println(de.Name(), de.IsDir())

}

}

因为它实现了io/fs.FS接口,所以可以返回它的子文件夹作为新的文件系统:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18


package main

import (

"embed"

"fmt"

"io/fs"

"io/ioutil"

)

//go:embed *

var f embed.FS

func main() {

ps, _ := fs.Sub(f, "p")

hi, _ := ps.Open("q/hi.txt")

data, _ := ioutil.ReadAll(hi)

fmt.Println(string(data))

}

应用

net/http

先前,我们提供一个静态文件的服务时,使用:


1


http.Handle("/", http.FileServer(http.Dir("/tmp")))

现在,io/fs.FS文件系统也可以转换成http.FileServer的参数了:


1

2

3

4

5


type FileSystem

func FS(fsys fs.FS) FileSystem

type Handler

func FileServer(root FileSystem) Handler

所以,嵌入文件可以使用下面的方式:


1


http.Handle("/", http.FileServer(http.FS(fsys)))

text/template和html/template.

同样的,template也可以从嵌入的文件系统中解析模板:


1

2


func ParseFS(fsys fs.FS, patterns ...string) (*Template, error)

func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error)

Go embed 简明教程相关推荐

  1. Go Embed简明教程

    Go Embed简明教程 go语言程序都是编译成二进制可执行文件的,但是实际执行时除了需要可执行程序,还需要一些静态文件,比如html模板等,于是就有人想如果Go官方能内建支持就好了.2019末一个提 ...

  2. Boost.Build 简明教程

    Boost.Build简明教程(译) Written by Boris Schäling. 历史: 2018-12-20 姚彧 第一版 目录 介绍 Introduction 生成过程 Build pr ...

  3. CGIC简明教程(转摘)

    CGIC简明教程 本系列的目的是演示如何使用C语言的CGI库"CGIC"完成Web开发的各种要求. *********************************     基础 ...

  4. kangle web server源代码安装简明教程

    kangle web server源代码安装简明教程 - kangle使用交流 - kangle软件 是一款高性能web服务器,反向代理服务器,提供虚拟主机管理系统及代理服务器,web服务器架设 - ...

  5. CentOs6.5中安装和配置vsftp简明教程

    这篇文章主要介绍了CentOs6.5中安装和配置vsftp简明教程,需要的朋友可以参考下 一.vsftp安装篇 复制代码代码如下: # 安装vsftpd yum -y install vsftpd # ...

  6. sqlalchemy mysql_SQLAlchemy简明教程

    原文可见:SQLAlchemy简明教程 - Jiajun的编程随想 SQLAlchemy是Python中常用的一个ORM,SQLAlchemy分成三部分: ORM,就是我们用类来表示数据库schema ...

  7. python tcp server_python scoket 编程 | tcp server client - 简明教程

    TCP 和 UDP 的区别 这两个协议都是传输层的协议,解决的问题,都是端口与端口的通信问题. TCP 每次建立通信,都需要三次握手,确定双方状态完毕,在发送数据.如果发送的数据出现了异常,TCP 也 ...

  8. 简明python教程pdf-python简明教程中文pdf

    python简明教程中文pdf电子书是作者通过自己对计算机语言多年来的研究和学习得出的学习经验,对于python新手来说非常有用,值得大家下载学习. python简明教程中文pdf文章目录 1.介绍 ...

  9. 简明python教程在线-Python简明教程

    Python简明教程在线阅读地址: https://bop.molun.net/ DocStrings 该文档字符串所约定的是一串多行字符串,其中第一行以某一大写字母开始,以句号结束.第二行为空行,后 ...

最新文章

  1. Interpreter - 解释器模式
  2. Android的矩阵(一):ColorMatrix
  3. 二、mysql数据类型
  4. 服务器麒麟系统能设置mtu吗,麒麟操作系统安装标准手册-20210405220006.docx-原创力文档...
  5. 涂抹果酱_如何玩果酱
  6. python 添加绝对路径时用反斜杠和正斜杠的区别
  7. vuecli3修改项目启动端口
  8. 右侧交易稳健获利的指标
  9. UITextField 和 UITextView实现字数限制 输入若干字以后就无法继续输入的功能
  10. SourcesTree使用手册2:文件更新
  11. java对【配置文件的读取】与【读配置文件时的路径问题】代码总结
  12. discuz 3.1 修改浏览器顶部小图标
  13. 齿轮刚度计算 matlab,数值积分求解齿轮刚度
  14. Android中免root的hook框架Legend原理解析
  15. CA65测试针对的是哪些产品呢
  16. 在opencv使用发现轮廓函数时出现-vector subscript out of range的问题
  17. 企业微信之微信接收消息(“暂不支持此消息类型”解决方案)
  18. MATLAB——直方图操作
  19. ROS2承上启下【05】:在单个进程中布置多个节点
  20. 用计算机玩王者荣耀,王者荣耀你们用电脑玩王者荣耀顺手吗? - 游戏发言 - 酷酷跑手机游戏...

热门文章

  1. SpringBoot继承TkMapper通用Mapper
  2. Nginx+Tpmcat 负载均衡
  3. 天梯赛L2-014 列车调度(set和简单贪心)
  4. python爬取b站弹幕分析_python爬取B站视频弹幕分析并制作词云
  5. Date对象 IOS踩坑
  6. 内存泄露部分检测工具
  7. mysql-bin日志文件清理
  8. ffmpeg合并音频(转)
  9. 利用日志传送来实现数据库的可用性
  10. EditText 双击才能获取点击事件