关于Golang获取当前项目绝对路径的问题

导读

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

用Go编写的程序有两种执行方式,go run和go 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会将源代码编译到系统TEMP或TMP环境变量目录中并启动执行;而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

嗯~~ 结果完全正确!

然后我再把构建好的程序扔到linux再运行后,它把我windows的路径给打印出来了 --!

[root@server app]# chmod +x demo
[root@server app]# ./demo
getCurrentAbPathByCaller = D:/Projects/demo

没想到白白高兴一场,这个时候我就在想,既然go run时可以通过runtime.Caller()获取到正确的结果,go build时也可以通过 os.Executable()来获取到正确的路径;

那如果我能判定当前程序是通过go run还是go build执行的,选择不同的路径获取方法,所有问题不就迎刃而解了吗。
区分程序是go run还是go build执行

Go没有提供接口让我们区分程序是go run还是go build执行,但我们可以换个思路来实现:

根据go run的执行原理,我们得知它会源代码编译到系统TEMP或TMP环境变量目录中并启动执行;

那我们可以直接在程序中对比os.Executable()获取到的路径是否与环境变量TEMP设置的路径相同, 如果相同,说明是通过go run启动的,因为当前执行路径是在TEMP目录;不同的话自然是go build的启动方式。

下面是完整代码:

package main

import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"runtime"
"strings"
)
func main() {
fmt.Println("getTmpDir(当前系统临时目录) = ", getTmpDir())
fmt.Println("getCurrentAbPathByExecutable(仅支持go build) = ", getCurrentAbPathByExecutable())
fmt.Println("getCurrentAbPathByCaller(仅支持go run) = ", getCurrentAbPathByCaller())
fmt.Println("getCurrentAbPath(最终方案-全兼容) = ", getCurrentAbPath())
}
// 最终方案-全兼容
func getCurrentAbPath() string {
dir := getCurrentAbPathByExecutable()
if strings.Contains(dir,getTmpDir()) {
return getCurrentAbPathByCaller()
return dir
// 获取系统临时目录,兼容go run
func getTmpDir() string {
dir := os.Getenv("TEMP")
if dir == "" {
dir = os.Getenv("TMP")
res, _ := filepath.EvalSymlinks(dir)
return res
// 获取当前执行文件绝对路径
func getCurrentAbPathByExecutable() string {
exePath, err := os.Executable()
if err != nil {
log.Fatal(err)
res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
// 获取当前执行文件绝对路径(go run)
func getCurrentAbPathByCaller() string {
var abPath string
_, filename, _, ok := runtime.Caller(0)
if ok {
abPath = path.Dir(filename)
return abPath

在windows执行

D:\Projects\demo>go run main.go
getTmpDir(当前系统临时目录) = C:\Users\XXX\AppData\Local\Temp
getCurrentAbPathByExecutable(仅支持go build) = C:\Users\XXX\AppData\Local\Temp\go-build456189690\b001\exe
getCurrentAbPathByCaller(仅支持go run) = D:/Projects/demo
getCurrentAbPath(最终方案-全兼容) = D:/Projects/demo
D:\Projects\demo>go build & demo.exe
getTmpDir(当前系统临时目录) = C:\Users\XXX\AppData\Local\Temp
getCurrentAbPathByExecutable(仅支持go build) = D:\Projects\demo
getCurrentAbPathByCaller(仅支持go run) = D:/Projects/demo
getCurrentAbPath(最终方案-全兼容) = D:\Projects\demo

在windows编译后上传到Linux执行

[root@server app]# pwd
/data/app
[root@server app]# ./demo
getTmpDir(当前系统临时目录) = .
getCurrentAbPathByExecutable(仅支持go build) = /data/app
getCurrentAbPathByCaller(仅支持go run) = D:/Projects/demo
getCurrentAbPath(最终方案-全兼容) = /data/app

对比结果,我们可以看到,在不同的系统中,不同的执行方式,我们封装的getCurrentAbPath方法最终都输出的正确的结果,perfect!

到此这篇关于关于Golang获取当前项目绝对路径的问题的文章就介绍到这了,更多相关Golang绝对路径内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • golang 如何删除二进制文件中的源码路径信息

    方法 go v1.13 go build 新增 -trimpath参数,不用以前那么麻烦了. ➜ awesomeProject CGO_ENABLED=0 go build -v -a -ldflags="-w -s" -trimpath \ -o ./hello_word hello_word.go ➜ awesomeProject strings hello_word|grep src ➜ awesomeProject #之前 ➜ awesomeProject go tool ob

  • golang 获取当前执行程序路径的操作

    我就废话不多说了,大家还是直接看代码吧~ 1.获取当前执行程序路径 func execPath() (string, error) { file, err := exec.LookPath(os.Args[0]) if err != nil { return "", err } re, err := filepath.Abs(file) if err != nil { logs.Error("The eacePath failed: %s\n", err.Error

  • 关于Golang获取当前项目绝对路径的问题

    导读 由于Golang是编译型语言(非脚本型语言),如果你想在Golang程序中获取当前执行目录将是一件非常蛋疼的事情.以前大家最折中的解决方案就是通过启动传参或是环境变量将路径手动传递到程序,而今天我在看日志库的时候发现了一种新的解决方案.Go程序两种不同的执行方式 用Go编写的程序有两种执行方式,go run和go build 通常的做法是go run用于本地开发,用一个命令中快速测试代码确实非常方便:在部署生产环境时,我们会通过go build构建出二进制文件然后上传到服务器再去执行. 两

  • java中获取类加载路径和项目根路径的5种方式分析

    复制代码 代码如下: package my; import java.io.File; import java.io.IOException; import java.net.URL; public class MyUrlDemo { public static void main(String[] args) {         MyUrlDemo muDemo = new MyUrlDemo();         try {             muDemo.showURL();    

  • Python 获取项目根路径的代码

    在 运行,调试,打包成exe 三个不同场景下获取跟路径,用于解决获取资源文件绝对路径问题. 工具类代码如下: import sys import os class pathutil(object): """路径处理工具类""" def __init__(self): # 判断调试模式 debug_vars = dict((a, b) for a, b in os.environ.items() if a.find('IPYTHONENABLE')

  • JAVA获取当前项目和文件所在路径的实例代码

    直接上代码: //当前项目下路径 File file = new File(""); String filePath = file.getCanonicalPath(); System.out.println(filePath); //当前项目下xml文件夹 File file1 = new File(""); String filePath1 = file1.getCanonicalPath()+File.separator+"xml\\";

  • springboot @Value实现获取计算机中绝对路径文件的内容

    目录 springboot @Value获取绝对路径文件的内容 默认情况下使用 同样的我们可以使用file协议获取文本的内容 Spring注解@Value解读 依赖注入概述 实际应用案例 源码解读分析 1.首先进入SpringApplication.run(XXXApplication.class, args) 2.然后进入到AbstractApplicationContext的refresh()方法 3.而后进入到DefaultListableBeanFactory的preInstantiat

  • 获取Django项目的全部url方法详解

    在为一个项目添加权限时,遇到一个问题,就是为项目所有的url设置权限,但是一个一个手动输入太麻烦了,所以考虑用代码获取到一个项目所有的url 首先,考虑到项目最外层的urlpartterns,因为所有的url都要通过这里 urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^arya/', site.urls), url(r'^index/', index), ] 先循环打印一下这个列表,看一下拿到的结果: <RegexURLRes

  • php获取网站根目录物理路径的几种方法(推荐)

    在PHP中获取网站根目录物理路径. 在php程序开发中经常需要获取当前网站的目录,我们可以通过常量定义获取站点根目录物理路径,方便在程序中使用. 下面介绍几种常用的获取网站根目录的方法. php获取网站根目录方法一: <?php define("WWWROOT",str_ireplace(str_replace("/","\\",$_SERVER['PHP_SELF']),'',__FILE__)."\\"); echo

  • PHP获取数组表示的路径方法分析【数组转字符串】 原创

    本文实例讲述了PHP获取数组表示的路径方法.分享给大家供大家参考,具体如下: 问题: 文件解析过程中发现一段路径用数组的形式存储,现需要将完整路径以字符串形式输出 解决方法: $hostspath=array('Windows','System32','drivers','etc','hosts'); $pathstr=''; foreach($hostspath as $k=>$v){ $pathstr.=$v.'/'; } $pathstr=substr($pathstr,0,-1); ec

  • Symfony2获取web目录绝对路径、相对路径、网址的方法

    本文实例讲述了Symfony2获取web目录绝对路径.相对路径.网址的方法.分享给大家供大家参考,具体如下: 对于你的需求,Symfony2通过DIC提供了kernel服务,以及request(请求)的封装. 在controller里(在其他地方你可以自行注入kernel,这个服务是HttpKernel库里的一个类:好孩子都喜欢读源代码): $appRoot = $this->get('kernel')->getRootDir(); // 这里得到的是app目录的绝对路径 // ... 然后你

  • xcode中获取js文件的路径方法(推荐)

    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"click" ofType:@"js"]; 理论上这样是应该能获取click.js的路径的,但是返回的确是null. 解决方法是: 项目的TARGETS-->Build Phases 在Compile Sources中 ,去掉你的js文件 在Copy Bundle Resources中,加上你的js文件 原理应该就是改之前Xcode默认把

随机推荐