我有一些从 Go 程序中读取的文本文件。我想发布一个可执行文件,而不额外提供该文本文件。如何将它嵌入到 Windows 和 Linux 上的编译中?
8 回答
===== 2021 年 1 月编辑 =====
从 2021 年 2 月发布的 Go 1.16 开始,您可以使用该go:embed
指令:
import "embed"
//go:embed hello.txt
var s string
print(s)
//go:embed hello.txt
var b []byte
print(string(b))
//go:embed hello.txt
var f embed.FS
data, _ := f.ReadFile("hello.txt")
print(string(data))
=====原始答案======
从 Go 1.4 开始,如果您需要更大的灵活性,可以使用go generate 。
如果您有多个文本文件或文本文件可能会更改,您可能不想硬编码文本文件,而是在编译时包含它。
如果您有以下文件:
main.go
scripts/includetxt.go
a.txt
b.txt
并且想要访问 main.go 中所有 .txt 文件的内容,您可以包含一个包含 go generate 命令的特殊注释。
main.go
package main
import "fmt"
//go:generate go run scripts/includetxt.go
func main() {
fmt.Println(a)
fmt.Println(b)
}
go generate 命令将在go:generate
. 在这种情况下,它运行一个 go 脚本,该脚本读取所有文本文件并将它们作为字符串文字输出到一个新文件中。我跳过了较短代码的错误处理。
脚本/includetxt.go
package main
import (
"io"
"io/ioutil"
"os"
"strings"
)
// Reads all .txt files in the current folder
// and encodes them as strings literals in textfiles.go
func main() {
fs, _ := ioutil.ReadDir(".")
out, _ := os.Create("textfiles.go")
out.Write([]byte("package main \n\nconst (\n"))
for _, f := range fs {
if strings.HasSuffix(f.Name(), ".txt") {
out.Write([]byte(strings.TrimSuffix(f.Name(), ".txt") + " = `"))
f, _ := os.Open(f.Name())
io.Copy(out, f)
out.Write([]byte("`\n"))
}
}
out.Write([]byte(")\n"))
}
要将所有 .txt 文件编译到可执行文件中:
$ go generate
$ go build -o main
现在您的目录结构将如下所示:
main.go
main
scripts/includetxt.go
textfiles.go
a.txt
b.txt
textfiles.go 是由 go generate 和 script/includetxt.go 生成的
文本文件.go
package main
const (
a = `hello`
b = `world`
)
运行 main 给出
$ ./main
hello
world
只要您对 UTF8 编码文件进行编码,这将正常工作。如果您想对其他文件进行编码,您可以使用 go 语言(或任何其他工具)的全部功能来执行此操作。我使用这种技术将png:s十六进制编码为单个可执行文件。这需要对 includetxt.go 进行微小的更改。
使用go-bindata。从自述文件:
该工具可将任何文件转换为可管理的 Go 源代码。用于将二进制数据嵌入到 go 程序中。文件数据在转换为原始字节切片之前可以选择进行 gzip 压缩。
正在寻找同样的东西并遇到了esc: Embedding Static Assets in Go(截至 2014 年 11 月 19 日),作者Matt Jibson正在评估其他 3 个声称可以进行文件嵌入的流行包:
- 拉基尔/statik
- jteeuwen/go-bindata(以及新的官方go-bindata/go-bindata和另一个改进的kevinburke/go-bindata)
- GeertJohan/go.rice
并解释为什么他最终想出了自己的包:
因此,在简单地尝试了所有这些(按顺序)之后,我自然而然地选择了 Matt 的esc,因为它是唯一一个开箱即用的具有我必需的功能(单个可执行文件中的 HTTPS 服务),即:
- 可以获取一些目录并以与http.FileSystem兼容的方式将所有文件递归地嵌入其中
- 可以选择禁用以与本地文件系统一起使用以进行本地开发,而无需更改客户端的代码
- 不会在后续运行中更改输出文件在文件更改时具有合理大小的差异
- 能够通过
//go:generate
而不是强迫您手动编写额外的 Go 代码来完成工作
第 2点对我来说很重要,而其他软件包由于某种原因并没有那么好。
来自 esc 的自述文件:
esc 将文件嵌入到 go 程序中,并为它们提供 http.FileSystem 接口。
它在指定路径的命名目录下递归添加所有命名文件或文件。输出文件提供了一个 http.FileSystem 接口,对标准库之外的包的依赖性为零。
您可以使用 astring literal
将文本定义为常量或变量。字符串文字是通过用反引号括起来的字符串来定义的。例如“字符串”。
例如:
package main
import "fmt"
func main() {
const text = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit
amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante
hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet
vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut
libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a
semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis.
Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut
convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis
quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis
parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae
nisi at sem facilisis semper ac in est.
`
fmt.Println(text)
}
检查packr,它使用起来非常友好
package main
import (
"net/http"
"github.com/gobuffalo/packr"
)
func main() {
box := packr.NewBox("./templates")
http.Handle("/", http.FileServer(box))
http.ListenAndServe(":3000", nil)
}
使用go1.16,您可以开始使用embed
,这是一个标准包,可帮助您将静态非 go 文件嵌入到二进制文件中
文档:https
://pkg.go.dev/embed
示例:https ://blog.carlmjohnson.net/post/2021/how-to-use-go-embed/
对于go < 1.16,您可以使用packr。这是一个很棒的工具,您可以在https://github.com/gobuffalo/packr查看更多相关信息
我使用一个简单的函数在运行中读取外部模板go generate
并从中生成 Go 代码。将生成一个将模板作为字符串返回的函数。然后可以使用解析返回的模板字符串tpl, err := template.New("myname").Parse(mynameTemplate())
我确实把那个代码放到了 github 上。您可能想尝试https://github.com/wlbr/templify
非常简单,但对我来说效果很好。
基于@CoreyOgburn 评论和这个Github 评论,创建了以下片段:
//go:generate statik -src=./html
package main
import (
_ "./statik"
"github.com/rakyll/statik/fs"
)
func statikFile() {
s, _ := fs.New()
f, _ := s.Open("/tmpl/login.html")
b, _ := ioutil.ReadAll(f)
t, _ := template.New("login").Parse(string(b))
t.Execute(w, nil)
}
并运行
go generate
随后
go build
应该创建一个包含文件的二进制文件