🔱

go:embedを使ってローカルにあるファイルを呼び出す方法

2023/03/02に公開

はじめに

ローカルにあるファイルを呼び出す方法として、go言語を使用して
プロジェクト配下にあるファイルであればファイルパスを指定してosなどを使用して読み込むことができますが

go buildコマンドでビルドした場合には.goファイル以外はビルドの対象にならないため
ビルドしたファイルを実行しても、読み込み対象のファイルが存在しないためエラーが発生してしまいます。

go:embedとは

go 1.16から追加されたpackageで
外部ファイルをプログラムの中に埋め込むための機能です。

これにより、プログラムと一緒に配布される必要のある設定ファイルやリソースファイルを、実行バイナリとしてパッケージングすることができます。
プログラム内で指定した、.goファイル以外を実行ファイルに組み込むことができるようになるので、デプロイや配布が容易になります。

ファイルを読み込むのではなく、ビルド時にバイナリファイルとして取り込んでいるので
ファイル読み込みのオーバーヘッドもなく気軽に使えそうです。
https://go.dev/doc/go1.16#library-embed

使い方

環境

go 1.16以降の環境を用意します
https://pkg.go.dev/embed
パッケージはこちらです

1つのファイルを読み込む

import _ "embed"

embed packageをimportします、
1つのファイルをインポートする場合は直接packageを使用しないのでブランクでimportしておきます

読み込み対象のファイル

hello.txt
hello
file/world.txt
world

1つのファイルを変数に格納する方法はこちら
あまり見慣れない書き方ですが
//go:embed のようにGoのディレクティブを使用して、コンパイル時にファイルを読み込んで変数に内容を格納しています
単一のファイルを取り込む場合はstringまたは[]byteを使用できます

main.go
//go:embed hello.txt
var hello string
//go:embed file/world.txt
var world []byte

func main() {
	fmt.Println(hello)
	fmt.Println(world)
}

https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives

上記を実行するとファイルの内容が読み取れています

❯❯❯ go run ./main.go
hello
world

ディレクトリ内の複数ファイルを読み込む

import "embed"

読み込み対象のファイル

./file
├── ./hello.txt
└── ./world.txt

読み込みたいファイルを読み込み対象のディレクトリに格納し

main.go
//go:embed file/*
var files embed.FS

func main() {
	hello, _ := files.ReadFile("file/hello.txt")
	world, _ := files.ReadFile("file/world.txt")
	fmt.Println(string(hello), string(world))
}

embed.FS型の変数に go:embedで対象のファイルを指定します
このとき、ワイルドカードを使用してファイルを指定することで複数のファイルやディレクトリを読み込めます。

プログラム内で利用するときは
files.ReadFile("file/world.txt")
のようにReadFileでファイル名を指定して呼び出します,os.File型の呼び出しと同じくファイルを指定して呼び出す形で呼び出せます。

embed.FS型

embed.FS型はio/fsパッケージのFSインターフェイスを実装しているため
net/http text/template html/template等、ファイルシステムを引数として取るpackageから簡単に呼び出すことができます

例 net/httpとの組み合わせ

起動しているhttpサーバのパスに、FSで指定したファイルを配置するだけで
コンテンツを配信することができます。

os.File等を使用して静的ファイルを配信する場合だと、goのビルドファイルには含まれないため
ビルドしたファイルとは別に静的ファイルを管理する必要がありましたが、embedでファイルを指定する場合は
ビルドファイル内に内包されるため、管理がとても楽になります。

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

例 text/templateとの組み合わせ

mail.tmpl
{{.company}}

お世話になっております。

弊社主催の「{{.seminar}}」にお申し込みいただき
誠にありがとうございました。
main.go
func main() {
	data := map[string]string{
		"company": "株式会社レスキューナウ",
		"seminar": "防災セミナー",
	}
	t, _ := template.New("mail.tmpl").ParseFS(files, "file/mail.tmpl")
	if err := t.Execute(os.Stdout, data); err != nil {
		log.Fatal(err)
	}
}

実行結果

株式会社レスキューナウ

お世話になっております。

弊社主催の「防災セミナー」にお申し込みいただき
誠にありがとうございました。

まとめ

この記事では//go:embedembed.FS型の使い方について記載しました
プログラムとしてはos.File型のファイル呼び出しとそこまで大きな変化はありませんが、外部リソースをプログラムに埋め込むことで、アプリケーションのデプロイが簡単になるので非常に使いやすいと感じました。

レスキューナウテックブログ

Discussion