Go Embed 簡明教程


title: "Go Embed 簡明教程"
date: 2021-03-07T14:53:56+08:00
draft: true
tags: ['go']
author: "dadigang"
author_cn: "大地缸"
personal: "http://www.real007.cn"


關于作者

http://www.real007.cn/about

Go embed 簡明教程

Go編譯的程序非常適合部署泽示,如果沒有通過CGO引用其它的庫的話验残,我們一般編譯出來的可執(zhí)行二進制文件都是單個的文件节预,非常適合復制和部署。在實際使用中听诸,除了二進制文件,可能還需要一些配置文件彼妻,或者靜態(tài)文件作儿,比如html模板、靜態(tài)的圖片舱沧、CSS妹沙、javascript等文件,如何這些文件也能打進到二進制文件中熟吏,那就太美妙距糖,我們只需復制、按照單個的可執(zhí)行文件即可牵寺。

一些開源的項目很久以前就開始做這方面的工作肾筐,比如 gobuffalo/packrmarkbates/pkger缸剪、 rakyll/statik吗铐、 knadh/stuffbin 等等,但是不管怎么說這些都是第三方提供的功能杏节,如果Go官方能內(nèi)建支持就好了唬渗。2019末一個提案被提出 issue#35950,期望Go官方編譯器支持嵌入靜態(tài)文件。后來Russ Cox專門寫了一個設計文檔 Go command support for embedded static assets, 并最終實現(xiàn)了它奋渔。

Go 1.16中包含了go embed的功能镊逝,而且Go1.16基本在一個月左右的時間就會發(fā)布了,到時候你可以嘗試使用它嫉鲸,如果你等不及了撑蒜,你也可以下載Go 1.16beta1嘗鮮。

本文將通過例子玄渗,詳細介紹go embed的各個功能座菠。

嵌入

  • 對于單個的文件,支持嵌入為字符串和 byte slice
  • 對于多個文件和文件夾藤树,支持嵌入為新的文件系統(tǒng)FS
  • 比如導入 "embed"包浴滴,即使無顯式的使用
  • go:embed指令用來嵌入,必須緊跟著嵌入后的變量名
  • 只支持嵌入為string, byte slice和embed.FS三種類型岁钓,這三種類型的別名(alias)和命名類型(如type S string)都不可以

嵌入為字符串

比如當前文件下有個hello.txt的文件升略,文件內(nèi)容為hello,world!。通過go:embed指令屡限,在編譯后下面程序中的s變量的值就變?yōu)榱?code>hello,world!品嚣。

12345678910111213
package main
import (    _ "embed"   "fmt")
//go:embed hello.txt
var s stringfunc main() {
    fmt.Println(s)
}

嵌入為byte slice

你還可以把單個文件的內(nèi)容嵌入為slice of byte,也就是一個字節(jié)數(shù)組钧大。

12345678910111213
package main
import (
        _ "embed"
        "fmt")
//go:embed hello.txt
var b []byte
func main() {
    fmt.Println(b)
}

嵌入為fs.FS

甚至你可以嵌入為一個文件系統(tǒng)翰撑,這在嵌入多個文件的時候非常有用。

比如嵌入一個文件:

1234567891011121314
package main
import (    "embed" "fmt")
//go:embed hello.txt
var f embed.FS
func main() {
    data, _ := f.ReadFile("hello.txt")
    fmt.Println(string(data))
}

嵌入本地的另外一個文件hello2.txt, 支持同一個變量上多個go:embed指令(嵌入為string或者byte slice是不能有多個go:embed指令的):

1234567891011121314151617
package main
import (    "embed"
    "fmt")
//go:embed hello.txt
//go:embed hello2.txt
var f embed.FS
func main() {
    data, _ := f.ReadFile("hello.txt")
    fmt.Println(string(data))
    data, _ = f.ReadFile("hello2.txt") 
   fmt.Println(string(data))
}

當前重復的go:embed指令嵌入為embed.FS是支持的拓型,相當于一個:

123456789101112131415
package main
import (
    "embed"
    "fmt")
//go:embed hello.txt
//go:embed hello.txt
var f embed.FS
func main() {
    data, _ := f.ReadFile("hello.txt")
    fmt.Println(string(data))
}

還可以嵌入子文件夾下的文件:

1234567891011121314151617
package main
import (    "embed" "fmt")
//go:embed p/hello.txt
//go:embed p/hello2.txt
var f embed.FS
func main() {
    data, _ := f.ReadFile("p/hello.txt")
    fmt.Println(string(data))
    data, _ = f.ReadFile("p/hello2.txt")
    fmt.Println(string(data))
}

還可以支持模式匹配的方式嵌入额嘿,下面的章節(jié)專門介紹瘸恼。

同一個文件嵌入為多個變量

比如下面的例子,s和s2變量都嵌入hello.txt的文件册养。

123456789101112131415161718
package main
import (    _
 "embed"
    "fmt")
//go:embed hello.txt
var s string
//go:embed hello.txt
var s2 string
func main() {
    fmt.Println(s)
    fmt.Println(s2)
}

exported/unexported的變量都支持

Go可以將文件可以嵌入為exported的變量东帅,也可以嵌入為unexported的變量。

123456789101112131415161718
package main
import (
    _ "embed"
    "fmt")
//go:embed hello.txt
var s string
//go:embed hello2.txt
var S string
func main() {
    fmt.Println(s)
    fmt.Println(S)
}

package級別的變量和局部變量都支持

前面的例子都是package一級的的變量球拦,即使是函數(shù)內(nèi)的局部變量靠闭,也都支持嵌入:

12345678910111213141516
package mainimport (    _ "embed"   "fmt")func main() { //go:embed hello.txt    var s string    //go:embed hello.txt    var s2 string   fmt.Println(s, s2)}

局部變量s的值在編譯時就已經(jīng)嵌入了,而且雖然s和s2嵌入同一個文件坎炼,但是它們的值在編譯的時候會使用初始化字段中的不同的值:

1234567891011121314
0x0021 00033 (/Users/....../main.go:10)        MOVQ    "".embed.1(SB), AX0x0028 00040 (/Users/....../main.go:10)        MOVQ    "".embed.1+8(SB), CX0x002f 00047 (/Users/....../main.go:13)        MOVQ    "".embed.2(SB), DX0x0036 00054 (/Users/....../main.go:13)        MOVQ    DX, "".s2.ptr+72(SP)0x003b 00059 (/Users/....../main.go:13)        MOVQ    "".embed.2+8(SB), BX......"".embed.1 SDATA size=16       0x0000 00 00 00 00 00 00 00 00 0d 00 00 00 00 00 00 00  ................       rel 0+8 t=1 go.string."hello, world!"+0"".embed.2 SDATA size=16       0x0000 00 00 00 00 00 00 00 00 0d 00 00 00 00 00 00 00  ................       rel 0+8 t=1 go.string."hello, world!"+0

注意s和s2的變量的值是在編譯期就確定了愧膀,即使在運行時你更改了hello.txt的文件,甚至把hello.txt都刪除了也不會改變和影響s和s2的值谣光。

只讀

嵌入的內(nèi)容是只讀的檩淋。也就是在編譯期嵌入文件的內(nèi)容是什么,那么在運行時的內(nèi)容也就是什么萄金。

FS文件系統(tǒng)值提供了打開和讀取的方法蟀悦,并沒有write的方法,也就是說FS實例是線程安全的氧敢,多個goroutine可以并發(fā)使用日戈。

1234
type FS    func (f FS) Open(name string) (fs.File, error)    func (f FS) ReadDir(name string) ([]fs.DirEntry, error)    func (f FS) ReadFile(name string) ([]byte, error)

go:embed指令

go:embed指令支持嵌入多個文件

1234567891011121314151617
package mainimport (    "embed" "fmt")//go:embed hello.txt hello2.txtvar f embed.FSfunc main() {    data, _ := f.ReadFile("hello.txt")  fmt.Println(string(data))   data, _ = f.ReadFile("hello2.txt")  fmt.Println(string(data))}

當然你也可以像前面的例子一樣寫成多行go:embed:

123456789101112131415161718
package mainimport (    "embed" "fmt")//go:embed hello.txt//go:embed hello2.txtvar f embed.FSfunc main() {  data, _ := f.ReadFile("hello.txt")  fmt.Println(string(data))   data, _ = f.ReadFile("hello2.txt")  fmt.Println(string(data))}

支持文件夾

文件夾分隔符采用正斜杠/,即使是windows系統(tǒng)也采用這個模式。

1234567891011121314151617
package mainimport (    "embed" "fmt")//go:embed pvar f embed.FSfunc main() {   data, _ := f.ReadFile("p/hello.txt")    fmt.Println(string(data))   data, _ = f.ReadFile("p/hello2.txt")    fmt.Println(string(data))}

使用的是相對路徑

相對路徑的根路徑是go源文件所在的文件夾孙乖。

支持使用雙引號"或者反引號的方式應用到嵌入的文件名或者文件夾名或者模式名上浙炼,這對名稱中帶空格或者特殊字符的文件文件夾有用。

1234567891011121314
package mainimport (    "embed" "fmt")//go:embed "he llo.txt" `hello-2.txt`var f embed.FSfunc main() {  data, _ := f.ReadFile("he llo.txt") fmt.Println(string(data))}

匹配模式

go:embed指令中可以只寫文件夾名唯袄,此文件夾中除了._開頭的文件和文件夾都會被嵌入弯屈,并且子文件夾也會被遞歸的嵌入,形成一個此文件夾的文件系統(tǒng)越妈。

如果想嵌入._開頭的文件和文件夾季俩, 比如p文件夾下的.hello.txt文件,那么就需要使用*梅掠,比如go:embed p/*

*不具有遞歸性店归,所以子文件夾下的._不會被嵌入阎抒,除非你在專門使用子文件夾的*進行嵌入:

1234567891011121314151617
package main
import (    "embed" "fmt")//go:embed p/*var f embed.FSfunc main() { data, _ := f.ReadFile("p/.hello.txt")   fmt.Println(string(data))   data, _ = f.ReadFile("p/q/.hi.txt") // 沒有嵌入 p/q/.hi.txt fmt.Println(string(data))}

嵌入和嵌入模式不支持絕對路徑、不支持路徑中包含...,如果想嵌入go源文件所在的路徑消痛,使用*:

1234567891011121314151617
package mainimport (    "embed" "fmt")//go:embed *var f embed.FSfunc main() {   data, _ := f.ReadFile("hello.txt")  fmt.Println(string(data))   data, _ = f.ReadFile(".hello.txt")  fmt.Println(string(data))}

文件系統(tǒng)

embed.FS實現(xiàn)了 io/fs.FS接口且叁,它可以打開一個文件,返回fs.File:

123456789101112131415
package mainimport (    "embed" "fmt")//go:embed *var f embed.FSfunc main() {   helloFile, _ := f.Open("hello.txt") stat, _ := helloFile.Stat() fmt.Println(stat.Name(), stat.Size())}

它還提供了ReadFileh和ReadDir功能秩伞,遍歷一個文件下的文件和文件夾信息:

12345678910111213141516
package mainimport (    "embed" "fmt")//go:embed *var f embed.FSfunc main() {   dirEntries, _ := f.ReadDir("p") for _, de := range dirEntries {     fmt.Println(de.Name(), de.IsDir())  }}

因為它實現(xiàn)了io/fs.FS接口逞带,所以可以返回它的子文件夾作為新的文件系統(tǒng):

123456789101112131415161718
package main
import (    "embed" "fmt"   "io/fs" "io/ioutil")//go:embed *var f embed.FSfunc main() { ps, _ := fs.Sub(f, "p") hi, _ := ps.Open("q/hi.txt")    data, _ := ioutil.ReadAll(hi)   fmt.Println(string(data))}

應用

net/http

先前欺矫,我們提供一個靜態(tài)文件的服務時,使用:

1
http.Handle("/", http.FileServer(http.Dir("/tmp")))

現(xiàn)在展氓,io/fs.FS文件系統(tǒng)也可以轉(zhuǎn)換成http.FileServer的參數(shù)了:

12345
type FileSystem    func FS(fsys fs.FS) FileSystemtype Handler    func FileServer(root FileSystem) Handler

所以穆趴,嵌入文件可以使用下面的方式:

1
http.Handle("/", http.FileServer(http.FS(fsys)))

text/template和html/template.

同樣的,template也可以從嵌入的文件系統(tǒng)中解析模板:

12
func ParseFS(fsys fs.FS, patterns ...string) (*Template, error)func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error)
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市遇汞,隨后出現(xiàn)的幾起案子未妹,更是在濱河造成了極大的恐慌,老刑警劉巖空入,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件络它,死亡現(xiàn)場離奇詭異,居然都是意外死亡歪赢,警方通過查閱死者的電腦和手機化戳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來埋凯,“玉大人点楼,你說我怎么就攤上這事〉蒺模” “怎么了盟步?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長躏结。 經(jīng)常有香客問我却盘,道長,這世上最難降的妖魔是什么媳拴? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任黄橘,我火速辦了婚禮,結果婚禮上屈溉,老公的妹妹穿的比我還像新娘塞关。我一直安慰自己,他們只是感情好子巾,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布帆赢。 她就那樣靜靜地躺著,像睡著了一般线梗。 火紅的嫁衣襯著肌膚如雪椰于。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天仪搔,我揣著相機與錄音瘾婿,去河邊找鬼。 笑死,一個胖子當著我的面吹牛偏陪,可吹牛的內(nèi)容都是我干的抢呆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼笛谦,長吁一口氣:“原來是場噩夢啊……” “哼抱虐!你這毒婦竟也來了?” 一聲冷哼從身側響起揪罕,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤梯码,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后好啰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轩娶,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年框往,在試婚紗的時候發(fā)現(xiàn)自己被綠了鳄抒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡椰弊,死狀恐怖许溅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秉版,我是刑警寧澤贤重,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站清焕,受9級特大地震影響并蝗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秸妥,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一滚停、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粥惧,春花似錦键畴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至咏删,卻和暖如春疤祭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背饵婆。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侨核。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓草穆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搓译。 傳聞我的和親對象是個殘疾皇子悲柱,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 概要 64學時 3.5學分 章節(jié)安排 電子商務網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,180評論 0 3
  • 為什么選擇GIT? Git自從2005年問世以來些己,已經(jīng)逐步成為本地和分布環(huán)境下版本控制的事實標準豌鸡。Git最早由Li...
    技匠閱讀 10,787評論 12 235
  • 目的這篇教程從用戶的角度出發(fā),全面地介紹了Hadoop Map/Reduce框架的各個方面段标。先決條件請先確認Had...
    SeanC52111閱讀 1,721評論 0 1
  • 以下內(nèi)容大多來自 傳送門涯冠,并根據(jù)其它資料以及自己的實際進行了修改整理,感謝原作者無私分享逼庞。 桌面環(huán)境配置 安裝完成...
    FiveStrong閱讀 13,579評論 2 20
  • awk簡介 awk是一種編程語言蛇更,用于在linux/unix下對文本和數(shù)據(jù)進行處理。數(shù)據(jù)可以來自標準輸入赛糟、一個或多...
    yeahuh閱讀 3,955評論 0 7