Goのembedを使う
はじめに
Goの1.16からembed packageがcoreライブラリに追加されています。結構面白い感じの機能だったのでちょっと試してみようかと思います。
go enbedとは
embedを利用すると静的ファイルをGoのプログラムに埋め込み、そこに対するアクセスを提供してくれます。embedでは以下の3つの形式でファイルを読み込むことができます
- string
- byte[]
- FS
ファイルの読み込みはパッケージのディレクトリかもしくはそのサブディレクトリから読み込むことができます。
使ってみる。
環境
動作環境は以下です。
$ go version go version go1.16 linux/amd64 $ uname -srvmpio Linux 5.4.0-66-generic #74-Ubuntu SMP Wed Jan 27 22:54:38 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal
単体のテキストファイルをstringやbyte[]として読み込む
まずは基本的な使い方として単体のテキストファイルの読み込みをしてみます。
//go:embed
ディレクティブをコメントとして記述することで利用することができます。
まずは以下のようなテキストファイルをembed
ディレクティブを記述するプログラムと同じファイル階層に置いておきます。
hello.txt
hello, world. this is embedded.
このテキストファイルをembed
でstringとして、読み込むためには以下のようにします。
package main import ( _ "embed" "fmt" ) //go:embed hello.txt var hello string func main() { fmt.Println(hello) }
実行してみます。
$ go build -o hello_embed $ ./hello_embed hello, world. this is embedded.
注目ポイントはふたつで、まずはembed
を利用するためにパッケージのブランクインポートが必要です。
もう一つのポイントはembed
ディレクティブは関数の外側でパッケージグローバルに定義しておく必要があるということで例えばプログラムを以下のように変更するとエラーで落ちます。
func main() { //go:embed hello.txt var hello string fmt.Println(hello) }
$ go build -o hello_embed # github.com/samuraiball/go-sandbox ./main.go:10:4: go:embed cannot apply to var inside func
読み込んだファイルを[]byte
で受け取りたい場合は単に以下のように書き換えるだけで大丈夫です。
//go:embed hello.txt var hello []byte func main() { fmt.Println(hello) fmt.Println(string(hello)) }
実行結果
[104 101 108 108 111 44 32 119 111 114 108 100 46 10 116 104 105 115 32 105 115 32 101 109 98 101 100 100 101 100 46] hello, world. this is embedded.
複数ファイルの読み込み
1つ以上の複数ファイルを読み込みたい場合はembed.FS
を受け取りの型として利用することができます。
例えばhtml
ディレクトリをembed
を行なうプログラムファイルと同じ階層に作り以下の2つを用意します。
index1.html
<!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 1</h1> </body> </html>
index2.thml
<!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 2</h1> </body> </html>
まずindex1.html
と、index2.html
を読み込むためには以下のようにします。
//go:embed html/* var html embed.FS func main() { htmlBytes, err := html.ReadFile("html/index1.html") if err != nil { log.Fatal(err) } fmt.Printf(string(htmlBytes)) }
前述している通り、embed.FS
で受け取ります。そして、個別のファイルを受け取る場合は[func (f FS) ReadFile(name string) ([]byte, error)](https://golang.org/pkg/embed/#FS.ReadFile)
を利用し、読み込みたいディレクトリ/ファイル名
を文字列で渡すと利用することができます。この戻り値は []byte
になります。
実行結果は以下の通りになります。
<!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 1</h1> </body> </html> ---------------------------------------------------- <!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 2</h1> </body> </html>
ここで、ファイルの読み込みはpath.Matchパターンでファイルを読み込むことができます例えば//go:embed html/*1.html
に書き換えて先程のコードを実行すると、index1.html
が読み込まれるようになるため、index2, err := html.ReadFile("html/index2.html")
のところで落ちるようになります。
2021/03/05 19:12:12 open html/index2.html: file does not exist
また、embed.FS
には他にも、func (f FS) Open(name string) (fs.File, error)とfunc (f FS) ReadDir(name string) ([]fs.DirEntry, error)も定義されており、それぞれfs.File
でファイルを読み込んだり、[]fs.DirEntry
を読み込んだりすることができます。
複数ディレクトリからまとめてファイルを読み込む
embed
は複数のディレクトリからファイルを読み込むことも可能です。
先程のindex1.html
とindex2.html
をそれぞれhtml1
とhtml2
ディレクトリを作成して格納し、それらを読み込みたい場合は以下のようにします。
//go:embed html1/* html2/* var html embed.FS func main() { index1, err := html.ReadFile("html1/index1.html") // エラーハンドリング省略 index2, err := html.ReadFile("html2/index2.html") // エラーハンドリング省略 fmt.Print(string(index1)) fmt.Println() fmt.Printf("----------------------------------------------------") fmt.Println() fmt.Print(string(index2)) }
実行結果は先ほどと同じです。
<!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 1</h1> </body> </html> ---------------------------------------------------- <!DOCTYPE html> <html lang="en"> <body> <h1>Hello, Embed files 2</h1> </body> </html>