Golang实现运行时动态编译与执行代码

Golang实现运行时动态编译与执行代码

能否在运行时将Go代码作为string动态编译并执行?

我刚开始学习golang。 期望的功能类似这样:

// main.go
package main

import (
     goc "some/package/to/compile/go"
)

func main() {
    goc.Execute(`
      some go code ...
   `)
}

因此,如果我运行该文件:

go run main.go

我期望能获得作为string编写的代码的输出。

是否有任何包可以实现此功能?

提前感谢


更多关于Golang实现运行时动态编译与执行代码的实战教程也可以访问 https://www.itying.com/category-94-b0.html

8 回复

是的,我之前没有考虑到安全问题。感谢提醒。

更多关于Golang实现运行时动态编译与执行代码的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你的命令行工具旨在解决什么根本问题?

或许你的命令行工具可以简单地先下载并安装Go工具链,然后使用它进行编译。Go的“安装”过程可以像下载一个tar.gz或zip文件并将其解压到某个位置一样简单。

谢谢,@christophberger

嗯,我之前在做一个 cli 工具,你可以用它从 URL 下载 源代码 并执行。

如果 源代码 没有 输入提示,一切都很顺利。现在为了实现这个功能,我一直在寻找方法,唯一想到的办法就是编译并执行 源代码

有没有其他方法可以完成这项工作?

假设我有一个来自URL的程序如下:

我希望用户已经安装了 go

package main
import "fmt"
func main() {
    var name string
    fmt.Print("Enter Name: ")
    fmt.Scanln(&name)
    fmt.Printf("Hello %v", name)
}

我希望上面的代码能作为我的程序(CLI名称:urlgo)的一部分来执行。

在终端中,如果我运行这个:

$ urlgo run <url>

期望的输出:

Enter Name: soubik
Hello soubik

除了编译 code 之外,还有其他方法可以实现这个吗?

有什么具体的应用场景吗?为什么你需要让代码作为程序的一部分来执行,而不是先编译代码,再将其作为外部可执行文件来调用?

也许有更简单的方法可以实现你的根本目标。

例如,URL背后的代码必须是Go代码吗?Go语言有一些可用的脚本语言集成方案,比如Lua或JavaScript。基本上,这些是提供了这些脚本语言解释器的包。如果你从URL下载的代码是Lua或JavaScript脚本,你可以直接在Go二进制文件中运行它。

另一个问题是,你真的想从URL下载并执行代码吗?请考虑其中涉及的所有安全风险。

你好,@soubikbhuiwk007,欢迎来到论坛!

你为什么会需要这个功能呢?

我之所以这样问,是因为编译 Go 需要的不仅仅是一个编译器包。整个工具链也必须可用,以便下载外部模块等。此外,标准库也必须可用才能正常工作。

因此,你可以直接在目标系统上安装 Go,然后让你的代码通过 os/exec 包来调用编译器。

也许可以看看 Go 的 REPL(读取-求值-打印-循环)是如何实现的。例如:

它们就是直接使用已安装的 Go 工具链。

soubikbhuiwk007:

除了编译code之外,还有其他方法可以实现这个吗?

你的第一个帖子是关于包管理器(例如 Linux 操作系统中的 aptrpmsnap 等;或者 Apple/Windows/Play 商店)的想法。你只需要专注于将其作为一个发展方向来开发,只需遵守某种分发规则(例如 DebianRepository/Format - Debian Wiki)或定义你自己的规则。

另外,我对你最新的例子感到困惑,它听起来像一个简单的下载器:你是在寻找 flag 库吗?如果你想要技术更高级的版本,cobra 和/或 viper 应该可以解决问题。至于下载,你可以参考很多下载示例(例如 Download an image or file from a URL in Go (Golang) - Welcome To Golang By Example)。

在我看来,对于下载器这个想法,下面提到的任何内容都过于复杂了。如果我打赌的话,你并不需要它们。我之所以列出来,是因为我不知道你具体在想什么,也不知道你的实际想法是什么。


如果你正在寻找 Go 中的动态脚本(例如,底层使用 cython 的 python)解释器,你可以看看 tengo 项目。不要去找“shell 解释器”库。如果我要用 shell,我直接用 os.Execute 来执行;它不需要侵入到 Go 二进制文件中。

如果你正在寻找类似动态加载的功能,Go 有原生的 plugin 库,可以让你动态加载已编译的 Go 二进制文件并即时使用它。

是的,Go语言支持在运行时动态编译和执行代码。虽然标准库没有直接提供这个功能,但可以通过go/parsergo/astgo/types等包结合plugin包或调用外部编译器来实现。以下是几种实现方式:

1. 使用plugin包(仅限Linux/macOS)

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "os/exec"
    "plugin"
)

func compileAndRun(code string) error {
    // 创建临时文件
    tmpfile, err := ioutil.TempFile("", "dynamic_*.go")
    if err != nil {
        return err
    }
    defer os.Remove(tmpfile.Name())

    // 写入代码
    if _, err := tmpfile.Write([]byte(code)); err != nil {
        return err
    }
    if err := tmpfile.Close(); err != nil {
        return err
    }

    // 编译为插件
    cmd := exec.Command("go", "build", "-buildmode=plugin", "-o", "plugin.so", tmpfile.Name())
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    if err := cmd.Run(); err != nil {
        return err
    }
    defer os.Remove("plugin.so")

    // 加载插件
    p, err := plugin.Open("plugin.so")
    if err != nil {
        return err
    }

    // 执行插件中的main函数
    sym, err := p.Lookup("Main")
    if err != nil {
        return err
    }
    if fn, ok := sym.(func()); ok {
        fn()
    }
    return nil
}

func main() {
    code := `
package main

import "fmt"

func Main() {
    fmt.Println("动态执行的代码")
    fmt.Println("1 + 2 =", 1+2)
}
`
    if err := compileAndRun(code); err != nil {
        fmt.Println("错误:", err)
    }
}

2. 使用go/ast生成代码并编译执行

package main

import (
    "fmt"
    "go/ast"
    "go/parser"
    "go/token"
    "io/ioutil"
    "os"
    "os/exec"
)

func executeDynamicCode(codeStr string) error {
    // 解析代码
    fset := token.NewFileSet()
    node, err := parser.ParseFile(fset, "dynamic.go", codeStr, parser.ParseComments)
    if err != nil {
        return err
    }

    // 创建AST
    file := &ast.File{
        Name:  ast.NewIdent("main"),
        Decls: node.Decls,
    }

    // 生成临时文件
    tmpfile, err := ioutil.TempFile("", "dynamic_*.go")
    if err != nil {
        return err
    }
    defer os.Remove(tmpfile.Name())

    // 写入格式化后的代码
    if err := format.Node(tmpfile, fset, file); err != nil {
        return err
    }
    tmpfile.Close()

    // 编译并执行
    cmd := exec.Command("go", "run", tmpfile.Name())
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    return cmd.Run()
}

func main() {
    code := `
package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        fmt.Printf("动态执行 %d\n", i)
    }
}
`
    if err := executeDynamicCode(code); err != nil {
        fmt.Println("执行错误:", err)
    }
}

3. 使用第三方库yaegi

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)
    
    // 执行动态代码
    code := `
package main

import "fmt"

func main() {
    fmt.Println("使用Yaegi解释器")
    result := 10 * 5
    fmt.Printf("10 * 5 = %d\n", result)
}
`
    
    _, err := i.Eval(code)
    if err != nil {
        fmt.Println("执行错误:", err)
    }
}

4. 完整示例:动态编译结构体和方法

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "os/exec"
    "path/filepath"
)

type DynamicExecutor struct{}

func (d *DynamicExecutor) Execute(code string, funcName string) error {
    // 创建完整程序
    fullCode := fmt.Sprintf(`
package main

import "fmt"

%s

func main() {
    %s()
}
`, code, funcName)

    // 创建临时目录
    tmpDir, err := ioutil.TempDir("", "godynamic_*")
    if err != nil {
        return err
    }
    defer os.RemoveAll(tmpDir)

    // 写入主文件
    mainFile := filepath.Join(tmpDir, "main.go")
    if err := ioutil.WriteFile(mainFile, []byte(fullCode), 0644); err != nil {
        return err
    }

    // 编译执行
    cmd := exec.Command("go", "run", mainFile)
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Dir = tmpDir
    
    return cmd.Run()
}

func main() {
    executor := &DynamicExecutor{}
    
    code := `
func DynamicFunc() {
    fmt.Println("动态函数执行")
    names := []string{"Alice", "Bob", "Charlie"}
    for i, name := range names {
        fmt.Printf("%d: %s\n", i+1, name)
    }
}
`
    
    if err := executor.Execute(code, "DynamicFunc"); err != nil {
        fmt.Println("错误:", err)
    }
}

注意事项:

  1. plugin包在Windows上有限制
  2. 动态执行代码存在安全风险
  3. 需要安装Go编译器环境
  4. 性能开销较大,不适合高性能场景

这些方法都能实现运行时动态编译和执行Go代码,选择哪种取决于具体需求。yaegi适合需要解释执行的场景,而plugin和外部编译适合需要完整Go功能的场景。

回到顶部