如何将文件嵌入到Go二进制文件中

huangapple go评论76阅读模式
英文:

How to embed files into Go binaries

问题

我有一些文本文件,我从我的Go程序中读取。我想要发布一个单独的可执行文件,而不需要额外提供那个文本文件。在Windows和Linux上,我该如何将它嵌入到编译中?

英文:

I have some text file that I read from my Go program. I'd like to ship a single executable, without supplying that text file additionally.
How do I embed it into compilation on Windows and Linux?

答案1

得分: 133

自Go 1.16开始(于2021年2月发布),您可以使用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脚本。为了简化代码,我省略了错误处理。

script/includetxt.go

package main

import (
    "io"
    "io/ioutil"
    "os"
    "strings"
)

// 读取当前文件夹中的所有.txt文件,并将它们编码为字符串文字写入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生成的。

textfiles.go

package main 

const (
a = `hello`
b = `world`
)

运行main会得到:

$ ./main
hello
world

只要您编码的是UTF8编码的文件,这将正常工作。如果要编码其他文件,您可以使用go语言(或任何其他工具)的全部功能来实现。我使用这种技术将png文件十六进制编码到一个单独的可执行文件中。这需要对includetxt.go进行微小的更改。

英文:

===== Edit Jan 2021 =====

Starting with Go 1.16, released in Feb 2021, you can use the go:embed directive:

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))

===== Original answer ======

Since Go 1.4, you can use go generate if you need more flexibility.

If you have more than one text file or the text file may change you might not want to hardcode the text file but include it at compile time.

If you have the following files:

main.go
scripts/includetxt.go
a.txt
b.txt

And want to have access to the contents of all .txt files in main.go, you can include a special comment containing a go generate command.

main.go

package main

import "fmt"

//go:generate go run scripts/includetxt.go

func main() {
    fmt.Println(a)
    fmt.Println(b)
}

The go generate command will run the script after go:generate. In this case it runs a go script which reads all text files and outputs them as string literals into a new file. I skipped the error handling for shorter code.

script/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"))
}

To compile all .txt files into your exectutable:

$ go generate
$ go build -o main

Now your directory structure will look like:

main.go
main
scripts/includetxt.go
textfiles.go
a.txt
b.txt

Where textfiles.go was generated by go generate and script/includetxt.go

textfiles.go

package main 

const (
a = `hello`
b = `world`
)

And running main gives

$ ./main
hello
world

This will work fine as long as you're encoding UTF8 encoded files. If you want to encode other files you have the full power of the go language (or any other tool) to do so. I used this technique to hex encode png:s into a single executable. That requires a minor change to includetxt.go.

答案2

得分: 32

使用go-bindata。从README中可以看到:

这个工具可以将任何文件转换为可管理的Go源代码。对于将二进制数据嵌入到Go程序中非常有用。文件数据在转换为原始字节切片之前可以选择进行gzip压缩。

英文:

Use go-bindata. From the README:

> This tool converts any file into managable Go source code. Useful for
> embedding binary data into a go program. The file data is optionally
> gzip compressed before being converted to a raw byte slice.

答案3

得分: 13

我正在寻找同样的东西,并找到了esc: Embedding Static Assets in Go(作者是Matt Jibson,于2014年11月19日发布),其中作者评估了其他3个声称可以进行文件嵌入的流行包:

  1. rakyll/statik
  2. jteeuwen/go-bindata(以及新的官方版本go-bindata/go-bindata和另一个改进版本kevinburke/go-bindata
  3. GeertJohan/go.rice

并解释了为什么他最终选择了自己的包:

  1. mjibson/esc

所以在简要尝试了它们之后(按照这个顺序),我自然而然地选择了Matt的esc,因为它是唯一一个能够直接使用(在一个可执行文件中)满足我需求的功能(HTTPS服务),具体来说:

  1. 可以将一些目录及其下的所有文件递归地嵌入,并与http.FileSystem兼容
  2. 可以选择禁用,以便在本地开发中与本地文件系统一起使用,而无需更改客户端的代码
  3. 在后续运行中不会更改输出文件,当文件更改时,差异大小合理
  4. 可以通过//go:generate来完成工作,而不是强制手动编写额外的Go代码

对于我来说,#2是重要的,而其他包由于某种原因都无法很好地满足需求。

从esc的README中可以看到:

> esc将文件嵌入到Go程序中,并为它们提供了http.FileSystem接口。
>
>它将所有命名的文件或以指定路径递归的文件添加到输出文件中。输出文件提供了一个http.FileSystem接口,不依赖于标准库之外的任何包。

英文:

Was looking for the same thing and came across esc: Embedding Static Assets in Go (by 19 Nov 2014) where author, Matt Jibson, is evaluating 3 other popular packages that claims to do file embedding:

  1. rakyll/statik
  2. jteeuwen/go-bindata (and the new official go-bindata/go-bindata and another improved one kevinburke/go-bindata)
  3. GeertJohan/go.rice

and explain why he eventually come up with his own package:

  1. mjibson/esc

So after briefly trying them all (in that order) I've naturally settled on Matt's esc as it was the only one that was working out of the box with necessary for me functionality (HTTPS service in a single executable), namely:

  1. Can take some directories and recursively embed all files in them in a way that was compatible with http.FileSystem
  2. Can optionally be disabled for use with the local file system for local development without changing the client's code
  3. Will not change the output file on subsequent runs has reasonable-sized diffs when files changed
  4. Capable of doing the work via //go:generate instead of forcing you to manually write additional Go code

The point #2 was important for me and the rest of the packages for one reason or another didn't work out that well.

From esc's README:

> esc embeds files into go programs and provides http.FileSystem interfaces to them.
>
>It adds all named files or files recursively under named directories at the path specified. The output file provides an http.FileSystem interface with zero dependencies on packages outside the standard library.

答案4

得分: 5

检查一下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)
}
英文:

check packr, its quite friendly to use

package main

import (
  "net/http"

  "github.com/gobuffalo/packr"
)

func main() {
  box := packr.NewBox("./templates")

  http.Handle("/", http.FileServer(box))
  http.ListenAndServe(":3000", nil)
}

答案5

得分: 5

使用go1.16,你可以开始使用**embed**,这是一个标准包,可以将静态非Go文件嵌入到你的二进制文件中。

文档:https://pkg.go.dev/embed <br>
示例:https://blog.carlmjohnson.net/post/2021/how-to-use-go-embed/

对于go < 1.16,你可以使用packr。这是一个很棒的工具,你可以在https://github.com/gobuffalo/packr了解更多信息。

英文:

With go1.16, you can start using embed, this is standard package which helps you embed static non-go files into your binary

Documentation: https://pkg.go.dev/embed <br>
Example: https://blog.carlmjohnson.net/post/2021/how-to-use-go-embed/

for go < 1.16, you can use packr. It's an awesome tool, you can check out more about this at https://github.com/gobuffalo/packr

答案6

得分: 4

你可以使用字符串字面量来将文本定义为常量或变量。字符串字面量通过用反引号括起字符串来定义。例如:`string`。

例如:

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)
}
英文:

You can use a string literal to define the text as a constant or variable. String literals are defined by enclosing the string with back-quotes. e.g. `string`.

For example:

package main

import &quot;fmt&quot;

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)
}

答案7

得分: 2

我使用了一个简单的函数来读取go generate运行中的外部模板,并从中生成Go代码。将生成一个返回模板字符串的函数。然后可以使用tpl, err := template.New("myname").Parse(mynameTemplate())来解析返回的模板字符串。

我将这段代码放在了github上。你可能想尝试一下https://github.com/wlbr/templify

非常简单,但对我来说效果很好。

英文:

I used a simple function to read an external template in a go generate run and to generate Go code from it. A function returning the template as a string will be generated. One can then parse the returned template string using tpl, err := template.New(&quot;myname&quot;).Parse(mynameTemplate())

I did put that code to github. You might want to try https://github.com/wlbr/templify

Very simple, but works for me quite well.

答案8

得分: 2

//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)
}

and run

go generate

and subsequently

go build

should create a binary that contains the files

英文:

Based on @CoreyOgburn comment and this Github comment, the following snippet was created:

//go:generate statik -src=./html

package main

import (
	_ &quot;./statik&quot;
	&quot;github.com/rakyll/statik/fs&quot;
)

func statikFile() {
    s, _ := fs.New()
    f, _ := s.Open(&quot;/tmpl/login.html&quot;)
    b, _ := ioutil.ReadAll(f)
    t, _ := template.New(&quot;login&quot;).Parse(string(b))
    t.Execute(w, nil)
}

and run

go generate

and subsequently

go build

should create a binary that contains the files

huangapple
  • 本文由 发表于 2013年7月23日 03:42:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/17796043.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定