前言
紀錄使用到其中一項 Go 1.16 的 embed 功能,可以把任何檔案在編譯時就包進去,進而不用煩惱路徑與環境問題。
使用時機
在寫某項權限功能時會需要讀取本地的 JSON 設定,我大可以直接讀取檔案使用它:
if data, err := os.ReadFile("./rbac.json"); err != nil { panic(err)}但可能會遇到:
- 部署到 Docker 後忘了把檔案 COPY 進去
- CI / 測試環境的工作目錄不同
- binary 被丟到其他機器執行,卻找不到相對路徑
- 想做成單一可執行檔發佈
單檔案
我自己在跑測試環境時就覺得動態抓檔案很麻煩。為了不讓程式依賴外部檔案設定,可以在編譯時就透過 embed 包進 binary:
package constants
import _ "embed"
//go:embed rbac.jsonvar RBACJSON []byteif err := json.Unmarshal(constants.RBACJSON, &parsed); err != nil { println("ERROR: Failed to unmarshal embedded rbac.json:", err.Error()) return}這個很像魔法的註解://go:embed 是一個編譯指令(compiler directive),編譯後,rbac.json 內容就已經存在 rbacData 變數中。
變數型別可以是:
string[]byteembed.FS
多檔案
package main
import ( _ "embed" "fmt")
//go:embed messages/*.txtvar messages embed.FS
func main() { files, _ := messages.ReadDir("messages") for _, file := range files { data, _ := messages.ReadFile("messages/" + file.Name()) fmt.Printf("File: %snContent: %snn", file.Name(), data) }}總結
| 比較項目 | os.ReadFile | embed |
|---|---|---|
| 檔案來源 | 執行時讀取 | 編譯時嵌入 |
| 是否依賴路徑 | 是 | 否 |
| 發佈方式 | 需帶檔案 | 單一 binary |
| 適合場景 | 動態變更檔案 | 靜態設定檔 |
如果資料需要在 runtime 被修改,那就不適合使用 embed,但如果它是「版本的一部分」,那 embed 是更安全的選擇。
- embed 是編譯時決定內容,修改檔案後必須重新編譯
- 檔案會增加 binary 體積,不適合嵌入大型資源(例如影片)
- 不能嵌入專案外的檔案