yaegi:讓你的 Go 代碼擁有動態腳本能力
在 Go 語言的世界裏,靜態編譯是其一大特色,能夠保證性能和安全性。然而,有些場景下,我們希望像 Python 或 JavaScript 一樣,支持運行時動態執行代碼,比如插件化架構、規則引擎、腳本解釋器等。今天,我們來介紹一個強大的 Go 解釋器庫——yaegi,它能讓 Go 代碼在運行時執行動態腳本。
yaegi 簡介
yaegi 是一個用 Go 語言編寫的 Go 解釋器。它可以在運行時解析和執行 Go 代碼,而無需重新編譯。
yaegi 適用於以下場景:
-
動態插件:支持熱加載插件,避免重新編譯主程序。
-
規則引擎:允許業務人員編寫 Go 代碼來調整業務邏輯。
-
嵌入式 Go 終端:提供 Go 代碼 REPL(交互式解釋器)。
-
安全沙箱:執行受限的 Go 代碼,防止惡意操作。
安裝 yaegi
使用 go install 安裝 yaegi CLI 工具:
go install github.com/traefik/yaegi/cmd/yaegi@latest
安裝完成後,可以在終端直接使用 yaegi 作爲 Go 代碼的 REPL:
➜ ~ yaegi>
fmt.Println("hello world")
hello world
或者在 Go 項目中引入 yaegi 庫:
go get github.com/traefik/yaegi
使用 yaegi 執行 Go 代碼
1. 直接執行 Go 代碼
最基礎的用法是直接解析並執行 Go 代碼:
package main
import (
"fmt"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
func main() {
i := interp.New(interp.Options{}) // 創建解釋器實例
i.Use(stdlib.Symbols) // 加載標準庫
_, err := i.Eval(`
package main
import "fmt"
func main() {
fmt.Println("Hello from Yaegi!")
}
`)
if err != nil {
fmt.Println("Error:", err)
}
// 調用 main 函數
_, err = i.Eval("main()")
if err != nil {
fmt.Println("Error:", err)
}
}
- 運行用戶自定義函數
我們可以在 yaegi 裏定義並執行 Go 函數:
package main
import (
"fmt"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
func main() {
// 創建解釋器實例
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols) // 加載標準庫
// 解釋並執行Go代碼
code := `
package main
func Add(a, b int) int {
return a + b
}
`
_, err := i.Eval(code) // 解析代碼
if err != nil {
fmt.Println("Error:", err)
return
}
// 獲取Add函數
v, err := i.Eval("Add")
if err != nil {
fmt.Println("Error:", err)
return
}
// 將v轉換爲函數
addFunc, ok := v.Interface().(func(int, int) int)
if !ok {
fmt.Println("Error: 類型轉換失敗")
return
}
// 調用Add函數
result := addFunc(3, 4)
fmt.Println("Result:", result) // 輸出:result
}
關鍵點解析
1. 初始化 yaegi 解釋器
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols)
-
interp.New 創建一個 yaegi 解釋器實例。
-
i.Use(stdlib.Symbols) 讓解釋器能夠使用標準庫(如 fmt、math 等)。
- 解析代碼
_, err := i.Eval(code)
-
Eval 解析並執行 Add 函數的定義。
-
由於 yaegi 需要完整的 Go 代碼塊,因此 code 裏必須包含 package main。
3. 獲取並調用 Add 函數
v, err := i.Eval("Add")
addFunc, ok := v.Interface().(func(int, int) int)
-
需要將其轉換爲 func(int, int) int 類型後才能調用。
-
v.Interface() 斷言爲 func(int, int) int 類型。
-
這樣就可以在 Go 代碼裏直接調用 Add(3, 4),並打印結果。
動態加載插件
yaegi 也可以用來動態加載 Go 插件,例如我們可以創建一個插件 plugin.go:
package main
import "fmt"
// 定義插件函數
func Hello(name string) {
fmt.Println("Hello,", name)
}
// 計算平方值
func Square(n int) int {
return n * n
}
此插件文件定義了兩個函數:
-
Hello(name string) —— 打印 “Hello, name”
-
Square(n int) int —— 返回 n 的平方
主程序 main.go,加載插件並調用函數
yaegi 解釋器會動態解析 plugin.go,並在運行時調用 Hello 和 Square 函數。
main.go(主程序,動態加載 plugin.go):
package main
import (
"fmt"
"os"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
func main() {
// 讀取插件代碼
pluginCode, err := os.ReadFile("plugin.go")
if err != nil {
fmt.Println("Error reading plugin file:", err)
return
}
// 創建解釋器實例
i := interp.New(interp.Options{})
i.Use(stdlib.Symbols) // 加載標準庫
// 解析插件代碼
_, err = i.Eval(string(pluginCode))
if err != nil {
fmt.Println("Error evaluating plugin:", err)
return
}
// 獲取Hello函數
v, err := i.Eval("Hello")
if err != nil {
fmt.Println("Error finding Hello function:", err)
return
}
// 轉換Hello爲可調用的函數
helloFunc, ok := v.Interface().(func(string))
if !ok {
fmt.Println("Error: 類型轉換失敗")
return
}
// 調用Hello函數
helloFunc("Yaegi")
// 獲取Square函數
v, err = i.Eval("Square")
if err != nil {
fmt.Println("Error finding Square function:", err)
return
}
// 轉換Square爲可調用的函數
squareFunc, ok := v.Interface().(func(int) int)
if !ok {
fmt.Println("Error: 類型轉換失敗")
return
}
// 調用Square函數
fmt.Println("Square(5):", squareFunc(5))
}
代碼解析
1. 首先 plugin.go 作爲動態插件,定義了 Hello 和 Square 函數。
2. 然後在主程序文件 main.go 中使用 yaegi 加載 plugin.go,並在運行時執行函數。
3. 調用 Eval 方法解析並獲取插件中的函數,然後通過 Interface() 轉換爲 Go 語言的可調用函數。
🚀 這樣,你就可以在不重新編譯的情況下,動態加載和執行 Go 插件了!真是泰褲辣!!!😍😍😍
安全性與限制
雖然 yaegi 提供了強大的動態執行能力,但也需要注意安全問題,比如避免執行不受信任的代碼。此外,yaegi 並不完全支持 Go 語言的所有特性,比如 cgo 和某些高級類型推導機制。如果你想在生產環境使用 yaegi,建議結合代碼沙箱機制和權限管理,避免執行未經審查的代碼。
總結
yaegi 讓 Golang 擁有了動態執行的能力,使得我們可以在不重新編譯的情況下,動態加載和運行 Go 代碼。它適用於規則引擎、插件系統和 REPL 等場景,但在生產環境使用時需要做好安全性控制。
如果你有動態執行 Go 代碼的需求,不妨試試 yaegi
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Ytii6RaMjGfGlmJPKR53bA