导读

由于Golang是编译型语言(非脚本型语言),如果你想在Golang程序中获取当前执行目录将是一件非常蛋疼的事情。 以前大家最折中的解决方案就是通过启动传参或是环境变量将路径手动传递到程序,而今天我在看日志库的时候发现了一种新的解决方案。

Go程序两种不同的执行方式

用Go编写的程序有两种执行方式,go rungo build

  • 通常的做法是go run用于本地开发,用一个命令中快速测试代码确实非常方便;
  • 在部署生产环境时,我们会通过go build构建出二进制文件然后上传到服务器再去执行。

两种启动方式会产生什么问题?

那么两种启动方式下,获取到当前执行路径会产生什么问题?

话不多说,我们直接上代码

我们编写获取当前可执行文件路径的方法

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
)

func main() {
	fmt.Println("getCurrentAbPathByExecutable = ", getCurrentAbPathByExecutable())
}


// 获取当前执行程序所在的绝对路径
func getCurrentAbPathByExecutable() string {
	exePath, err := os.Executable()
	if err != nil {
		log.Fatal(err)
	}
	res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
	return res
}

 

首先通过go run启动

D:\Projects\demo>go run main.go
getCurrentAbPathByExecutable =  C:\Users\XXX\AppData\Local\Temp\go-build216571510\b001\exe

再尝试go build执行

D:\Projects\demo>go build & demo.exe
getCurrentAbPathByExecutable =  D:\Projects\demo

通过对比执行结果,我们发现两种执行方式,我们获取到了不同的路径。而且很明显,go run获取到的路径是错误的。

 

原因: 这是由于go run会将源代码编译到系统TEMPTMP环境变量目录中并启动执行;而go build只会在当前目录编译出可执行文件,并不会自动执行。

我们可以简单理解为,go run main.go等价于go build & ./main

虽然两种执行方式最终都是一样的过程:源码->编译->可执行文件->执行输出,但他们的执行目录却完全不一样了。

 

新的方案诞生

这是在我今天查看服务日志(zap库)的时候,突然反应过来一件事情。 比如下面是一条简单的日志,而服务是通过go run启动的,但日志库却把我正确的程序路径D:/Projects/te-server/modules/es/es.go:139给打印出来了

2021-03-26 17:47:06    D:/Projects/te-server/modules/es/es.go:139  update es index {"index": "tags", "data": "[200 OK] {\"acknowledged\":true}"}

于是我马上去翻看zap源码,发现是通过runtime.Caller()实现的,其实所有Golang日志库都会有runtime.Caller()这个调用。

 

我开心的以为找到了最终答案,然后写代码试了下: 

package main

import (
	"fmt"
	"path"
	"runtime"
)

func main() {
	fmt.Println("getCurrentAbPathByCaller = ", getCurrentAbPathByCaller())

}

// 获取当前执行文件绝对路径(go run)
func getCurrentAbPathByCaller() string {
	var abPath string
	_, filename, _, ok := runtime.Caller(0)
	if ok {
		abPath = path.Dir(filename)
	}
	return abPath
}

 

首先在windows下面go run 和go build试一下

D:\Projects\demo>go run main.go
getCurrentAbPathByCaller =  D:/Projects/demo


D:\Projects\demo>go build & demo.exe
getCurrentAbPathByCaller =  D:/Projects/demo

嗯~~ 结果完全正确!!