embed包 提供了访问正在运行的go程序的功能。

  • 嵌入一个文件到string
import _ "embed"

//go:embed hello.txt
var s string
print(s)
  • 嵌入一个文件到一个字节切片
import _ "embed"

//go:embed hello.txt
var b []byte
print(string(b))
  • 嵌入一个或多个文件作为一个文件系统
import "embed"

//go:embed hello.txt
var f embed.FS
data, _ := f.ReadFile("hello.txt")
print(string(data))

usage

变量声明上面的go:embed指令使用一个或多个路径指定要嵌入的文件。匹配模式。

该指令必须紧接在包含单个变量声明的行之前。 指令和声明之间仅允许使用空行//行注释。

变量的类型必须是这三种的其中一个string, []byte, FS(或者FS的别名)。

package server

import "embed"

// content holds our static web server content.
//go:embed image/* template/*
//go:embed html/index.html
var content embed.FS

//go:embed指令为简洁起见接受多个空格分隔的模式,但它也可以重复,以避免有许多模式时出现很长的行。模式是相对于包含源文件的包目录进行解释的。即使在windows上,路径分隔符也是/(forward slash)。模式不能包含...或空路径元素,也不能以/开头或者结尾。使用*来匹配当前路径下的所有内容。为了允许命名名称中有空格的文件,可以将模式写为Go的双引号""或者反引号```` ``字符串文本

如果一个模式命名为一个目录,则该目录下的子树中所有文件都将被递归嵌入,但是名称以.(隐藏文件)或\开头的文件除外。所以上面例子中的变量几乎等价于:

// content is our static web server content.
//go:embed image template html/index.html
var content embed.FS

区别就是image/*嵌入了image/.tempfile,但是image没有。

//go:embed指令可以与导出和未导出的变量一起使用,具体取决于软件包是否希望使数据可用于其他软件包。 它只能与程序包范围的全局变量一起使用,而不能与局部变量一起使用

func Partial() {
    //go:embed test.txt
    vat a string
}

编译将会报错:

//go:embed only allowed in Go files that import "embed"

模式不得与包模块外部的文件匹配,例如 .git/*或符号链接。 空目录的匹配将被忽略。 之后,//go:embed行中的每个模式必须至少匹配一个文件或非空目录。

如果任何模式无效或匹配无效,则构建将失败。

File System

FS实现了io/fs包的FS接口,所以它能在任何理解文件系统的包内使用,包括net/http, text/template, 和html/template

http.Handle("/static/",http.StripPrefix("/static/",http.FileServer(http.FS(content))))

template.ParseFS(content, "*.tmpl")

无效模式

https://github.com/golang/go/issues/44486

embed的有效文件名不能为.git, .hg, .bzr, .svn,并且不能包含* < > ? ` ' | / \ : 等在某些Shell或操作系统中具有特殊含义的符号。

// isBadEmbedName reports whether name is the base name of a file that
// can't or won't be included in modules and therefore shouldn't be treated
// as existing for embedding.
func isBadEmbedName(name string) bool {
	if err := module.CheckFilePath(name); err != nil {
		return true
	}
	switch name {
	// Empty string should be impossible but make it bad.
	case "":
		return true
	// Version control directories won't be present in module.
	case ".bzr", ".hg", ".git", ".svn":
		return true
	}
	return false
}

// CheckFilePath checks that a slash-separated file path is valid.
// The definition of a valid file path is the same as the definition
// of a valid import path except that the set of allowed characters is larger:
// all Unicode letters, ASCII digits, the ASCII space character (U+0020),
// and the ASCII punctuation characters
// “!#$%&()+,-.=@[]^_{}~”.
// (The excluded punctuation characters, " * < > ? ` ' | / \ and :,
// have special meanings in certain shells or operating systems.)
//
// CheckFilePath may be less restrictive in the future, but see the
// top-level package documentation for additional information about
// subtleties of Unicode.
func CheckFilePath(path string) error {
	if err := checkPath(path, true); err != nil {
		return fmt.Errorf("malformed file path %q: %v", path, err)
	}
	return nil
}

如果非要支持带特殊字符的话,可以通过重新编译go二进制来实现。