服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

三种获取Go项目根目录的方式,让你做架构,选哪种?

日期: 来源:脑子进煎鱼了收集编辑:卡尔文_

大家好,这里是每周都陪你进步的网管~

在搭建项目中一般都会有确定项目根目录的绝对路径的需求,一旦有了根目录的绝对路径,就能以这个根目录为基准,设置静态文件、配置文件所在的目录,这样做的好处是无论把项目部署到哪个目录下,执行程序时都不会出现No such file or directory 这样的错误。

今天就总结一下在 Go 程序里边怎么获取项目的根目录绝对路径。在网上搜索怎么获取 Go 项目的根目录,一般有三种,分别依赖 Go 的以下三个底层函数实现:

  • os.Getwd() 
  • os.Args[0]
  • runtime.Caller

虽然这三种方式都能获取到Go项目的根目录,但前两种方式在某些情况下拿到的结果并不是我们想要的,只有使用第三种才是在所有执行环境下都能正确拿到Go项目的根目录路径。

我们接下来对这三种方法一一进行解释。首先我们来探讨一下为什么我们要在程序里拿到项目的根目录路径。

为什么需要项目根目录路径

这个问题其实开头已经提过了,假如一个项目有如下这样的目录结构

.
|-- config
|   `-- config.go
|   `-- config_dev.yaml
|-- main.go
|-- go.mod
|-- go.sum

假设我们要在config.go 中使用 viper库把config_dev.yaml中的配置项加载到内存中,这里看到config.goconfig_dev.yaml在同一个目录里,一般人都会这么设置配置文件的路径:

vp.AddConfigPath("./")
vp.SetConfigType("yaml")

乍一看起来,没啥毛病,不过假如运行下程序你就会发现完全不行,.虽然代表当前目录,但在Go语言里边它不代表是当前代码文件所在的目录,而是代表执行程序可执行文件的目录。

比如执行下面一套操作后

cd /Code/demo
go build -o demo.app
./demo.app

刚才我们在代码里写的".",它代表的是/Code/demo这个目录

PS,并不是所有语言都是这样,如果是Java程序,"."就是代表当前代码文件的文件目录。

好,搞清楚了我们为什么要费劲获取Go项目的根目录后,我们来说下三种获取他们的方法,以及为什么前两种不够通用。

os.Getwd

os.Getwd() 方法,Go 语言os包的Getwd函数能够获取进程的当前工作目录,其实从函数名字中的wd—working directory (工作目录)的缩写,差不多就能猜出来它的功能。

os.Getwd 改进我们上面的程序,能保证拿到的项目根目录是正确的吗?我们先用它改一下程序试试。

wd, _ := os.Getwd()
// 输出目录,看看路径对不对
fmt.Println("工作目录: " + wd)
// 用工作目录拼接出正确的配置文件目录
vp.AddConfigPath(wd +"/config")
vp.SetConfigType("yaml")

打包执行一下程序,会输出:

cd /Code/demo
go build -o demo.app
./demo.app
=== 以下是输出内容 ===
工作目录: /Code/demo

看起来没什么问题,不过刚才我们是在项目的根目录下编译并执行的程序,假如我们切换到其他目录执行呢?

cd /Users/xxx
/Code/demo/demo.app
=== 以下是执行后的输出内容 ===
工作目录:/Users/xxx

切换目录后再执行程序,发现程序中输出的工作目录变成了/Users/xxx,此时后面用它拼接出项目的配置文件目录的代码自然是不对的。

所以os.Getwd()这个方法获取的是进程在OS系统所在的目录,仅当在可执行文件所在的目录下启动程序的情况下才能正确拿到 Go 项目的根目录,这种情况还是不够通用的,需要与运维约定项目的启动命令才行。

os.Args[0]

接下来我们看第二种方式,os.Args这个列表里保存的是程序的启动参数,而参数0按照约定是程序的可执行文件名。

下面我们用它改进一下我们的程序

filePath, _ := exec.LookPath(os.Args[0])
absFilePath, _ := filepath.Abs(filePath)
rootDir := path.Dir(absFilePath)
// 输出目录,看看路径对不对
fmt.Println("程序根目录: " + rootDir)
// 用程序根目录拼接出正确的配置文件目录
vp.AddConfigPath(rootDir +"/config")
vp.SetConfigType("yaml")

打包编译程序后,试着在程序所在目录和其他的目录下都执行一下程序,看看程序能不能拿到正确的路径

cd /Code/demo
go build -o demo.app
./demo.app
=== 以下是执行后的输出内容 ===
程序根目录:/Code/demo

cd /Users/xxx
/Code/demo/demo.app
=== 以下是执行后的输出内容 ===
程序根目录:/Code/demo

通过上面的输出我们能看到,这两种情况都能够正确拿到程序的目录路径,os.Args[0] 在这两种情况下的值分别是./demo.app/Code/demo/demo.app。示例程序里之后的两个方法调用会帮我们找到可执行文件所在目录的绝对路径。

这种方式看起来挺完美,不过有一种情况它是满足不了的,就是,如果我们在研发阶段用go run启动程序的时候是不行的,此时程序会输出一个临时目录。

/var/folders/3g/f2sh8sgs5ls_z62npf80v69w0000gn/T/go-build1053443992/b001/exe

接下来我们再看第三种方法,它能适配各种情况。

runtime.Caller

想获取到程序的根目录,如果能拿到当前正在执行的代码的文件路径,我们也就能推断出程序的根目录了。怎么能拿到当前正在执行的代码的文件路径呢?

之前我们在介绍日志库Zap的时候说过,好的日志是需要记录下来当时的现场信息的--比如记录日志的代码所在的文件路径、行号、函数名等。这些信息怎么获取到的呢?当时看了源码后我们发现用的是 runtime.Caller()

func Caller(skip int) (pc uintptr, file string, line int, ok bool)

所以我们在config.go中,能这样获取当前文件的路径:

// 获取当前文件的路径
 _, filename, _, _ := runtime.Caller(0)

runtime.Caller 这里不在详细介绍,感兴趣的请看这两篇旧文

用它再推断出项目的根目录即可

root := path.Dir(path.Dir(filename))

用这种方法改造代码后,我们再用上面的几种启动方式,会看到都能正确获取到程序的根目录。

应该用哪种方式?

应该用哪种方式呢?其实没有固定答案,第三种方式更通用,不论是在开发阶段使用 go run ,还是我们在项目里写单元测试,用InteliJ IDEA 这样的IDE帮我们执行单元测试的时候,都是把程序放到一个临时目录执行的,所以第三种方式更通用一些。

如果是在生产环境启动项目,要是能跟运维约定好启动命令,用前两种方式也是没有问题的,甚至我们可以让运维在系统里设置ROOTDIR之类的环境变量,把根目录放在环境变量里,在程序里用os.Getenv("ROOTDIR")取也行。

如果让你架构项目,你会用哪种方式呢?评论区里说说吧,喜欢今天的文章欢迎转发和点赞,我们下期再见。

觉得有用就点个在看 

相关阅读

  • Go 标准库 net/http库知道吗?能说说优缺点吗?

  • 前言哈喽,大家后,我是asong;这几天看了一下Go语言标准库net/http的源码,所以就来分享一下我的学习心得;为什么会突然想看http标准库呢?因为在面试的时候面试官问我你知道Go语言的n
  • Go版本大于1.13,程序里这样做错误处理才地道

  • 大家好,这里是每周都在陪你进步的网管。之前写过几篇关于 Go 错误处理的文章,发现文章里不少知识点都有点落伍了,比如Go在1.13后对错误处理增加了一些支持,最大的变化就是支持了
  • 醒醒吧,未来不会有 Go2 了!

  • 大家好,我是煎鱼。马上春节了,节前最后一更。提前预祝大家春节快乐!本周末在学习的时候,看到 Go 团队大当家 Russ Cox(下称:rsc)在近期分享的《GopherCon 2022: Russ Cox - Compati
  • 紧急提醒!福州严查!

  • 2022年以来,全市市场监管部门深入开展整治校外教育培训机构不规范问题、减轻中小学生课外负担“点题整治”行动,查处了一批校外培训机构违法违规案件,促进全市校外培训机构健康
  • 突然窒息,仍在抢救……注意,最近高发

  • 1月14日湖北宜昌三峡中心人民医院西陵院区急诊科仅下半夜就收治了5名醉汉有的喝得人事不省有的呕吐得一塌糊涂在宜昌三峡中心人民医院伍家院区急诊科每天也都会收治醉汉所谓
  • 苦难的2022,仍需要避世的“主题公园”。

  • 点击收听-评《阿凡达2:水之道》:约5.3分杨超:5分《长江图》导演,《不要抬头》等19期嘉宾这便是“元宇宙电影”的雏形。当你们还在“科幻”或“电影”的维度讨论它是否合格之时,
  • 秒播TV端电视,近期最好用的版本!

  • 1、测试设备测评软件神袅电视软件适用TV端测评设备当贝X3测评结果无限制版本2、神袅电视【TV端】(软件链接在文章底部)很多小伙伴说,有没有新的电视直播分享一些?那么,那就安排一
  • UE4 材质练习 之 基础操作

  • 最近在学习 UE4 虚幻引擎,正好项目中也有用到,顺便记录一下相关内容,欢迎大家交流讨论,来不及解释了,快上车~~基础概念使用 UE 创建材质后,默认状态如下图所示,这里面有很多参数设

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • Go 标准库 net/http库知道吗?能说说优缺点吗?

  • 前言哈喽,大家后,我是asong;这几天看了一下Go语言标准库net/http的源码,所以就来分享一下我的学习心得;为什么会突然想看http标准库呢?因为在面试的时候面试官问我你知道Go语言的n
  • Go版本大于1.13,程序里这样做错误处理才地道

  • 大家好,这里是每周都在陪你进步的网管。之前写过几篇关于 Go 错误处理的文章,发现文章里不少知识点都有点落伍了,比如Go在1.13后对错误处理增加了一些支持,最大的变化就是支持了
  • 醒醒吧,未来不会有 Go2 了!

  • 大家好,我是煎鱼。马上春节了,节前最后一更。提前预祝大家春节快乐!本周末在学习的时候,看到 Go 团队大当家 Russ Cox(下称:rsc)在近期分享的《GopherCon 2022: Russ Cox - Compati