当前位置: 首页 > 科技观察

说说Go的相对路径问题

时间:2023-03-18 22:14:33 科技观察

本文转载自微信公众号《我的大脑是炸鱼》,作者陈建宇。转载本文请联系脑筋急转弯公众号。大家好,我是炸鱼。Go语言有多种运行模式,如何正确引用文件路径成为一个值得商榷的问题。以我的一个老Demogin-blog为例,我们在项目根目录下运行。执行gorunmain.go时是否能正常运行,执行gobuild也正常。如下:[$gin-blog]#gorunmain.go[GIN-debug][WARNING]Runningin"debug"mode。在生产中切换到“发布”模式。-usingenv:exportGIN_MODE=release-usingcode:gin.SetMode(gin.ReleaseMode)[GIN-debug]GET/api/v1/tags-->gin-blog/routers/api/v1.GetTags(3handlers)...它在不同的目录级别下如何以不同的方式工作?让我们带着问题学习吧!对于gorun,我们向上移动目录级别,转到$GOPATH/src,然后执行gorungin-blog/main.go[$src]#gorungin-blog/main.go2018/03/1216:06:13Failtoparse'conf/app.ini':openconf/app.ini:nosuchfileordirectoryexitstatus1gobuild使用gobuild命令执行./gin-blog/main。如下:[$src]#./gin-blog/main2018/03/1216:49:35Failtoparse'conf/app.ini':openconf/app.ini:nosuchfileordirectory这个时候你要打个大大的问号,这是我的程序在哪里读取的?我们通过分析知道,Go运行的相对路径是相对于执行命令时的目录的,自然是读取不到的。既然知道了问题出在哪里,我们就可以考虑怎么办了:)我们认为相对路径就是相对于执行命令的目录,这样就可以得到可执行文件的地址,拼接起来。在实践中,我们编写获取当前可执行文件路径的方法:import("path/filepath""os""os/exec""string")funcGetAppPath()string{file,_:=exec.LookPath(os.args[0])path,_:=filepath.Abs??(file)index:=strings.LastIndex(path,string(os.PathSeparator))returnpath[:index]}放在启动代码中查看路径:log.Println(GetAppPath())我们分别执行下面两条命令,查看输出结果。1.gorun$gorunmain.go2018/03/1218:45:40/tmp/go-build962610262/b001/exe2,gobuild$./main2018/03/1218:49:44$GOPATH/src/gin-blog分析我们关注gorun的输出,发现是一个临时文件的地址。为什么?在gohelprun中,我们可以看到:RuncompilesandrunsthemainpackagecomprisingthenamedGosourcefiles.AGosourcefileisdefinedtobeafileendinginaliteral.go"后缀。即当执行gorun时,文件会放到/tmp/go-build...目录下,编译运行。所以gorunmain.go的结果出现/tmp/go-build962610262/b001/exe也就不足为奇了,因为它已经运行到临时目录执行可执行文件了。想到这里已经很清楚了,那我们想想,会出现什么问题。如下:依赖相对路径的文件存在路径错误的问题。gorun和gobuild不同,一个可以在临时目录下执行,一个可以在编译好的目录下手动执行,路径处理不同。不断的gorun,不断的产生新的临时文件。这其实是根本原因,因为gorun和gobuild编译后的文件执行路径不一样,执行层次也可能不一样。自然而然的就出现了各种看不懂的奇怪问题。解决方法一、获取编译后的可执行文件的路径1、结合配置文件的相对路径和GetAppPath()的结果,解决gobuildmain.go的可执行文件跨目录执行的问题(如:./src/gin-blog/main)import("path/filepath""os""os/exec""string")funcGetAppPath()string{file,_:=exec.LookPath(os.Args[0])path,_:=filepath.Abs??(file)index:=strings.LastIndex(path,string(os.PathSeparator))returnpath[:index]}但是这个方法对于gorun还是无效的,需要2来补救这。2.通过传参指定路径可以解决gorunpackagemainimport("flag""fmt")funcmain(){varappPathstringflag.StringVar(&appPath,"app-path","app-path")flag.Parse()fmt.Printf("Apppath:%s",appPath)}run:gorunmain.go--app-path"Yourprojectaddress"2、添加os.Getwd()进行多层判断,参见beegoreadapp.conf代码。这种写法兼容gobuild,在项目根目录下执行gorun,但是跨目录执行gorun就不行了。3、配置全局系统变量我们可以通过os.Getenv获取系统全局变量,然后用相对路径拼接。1、设置项目工作空间简单来说就是设置项目(应用程序)的工作路径,然后将其与配置文件、日志文件等相对路径拼接,实现相对绝对路径,保证路径一致.查看gogs代码读取GOGS_WORK_DIR进行拼接。2、使用系统自带变量简单来说就是使用系统自带的全局变量,比如$HOME,将配置文件存放在$HOME/conf或/etc/conf中。这样配置文件可以更固定的存放,不需要额外设置环境变量。展开gotest在某些场景下也会遇到路径问题,因为gotest只能在当前目录下执行,所以在执行测试用例的时候,你的执行目录已经是test目录了。需要注意的是,如果采用获取外部参数的方式,在使用os.args时,gotest-args、gorun、gobuild会出现命令行参数位置不一致的情况。总结这三种方案,在目前可见的开源项目或介绍中都能找到。优缺点也很明显,我觉得针对不同的项目选择合适的方案就够了。建议大家不要太依赖读取配置文件的模块,而是应该“堆叠”起来注册需要什么配置变量,这样可以解决一部分问题。大家有什么看法,一起来讨论一下吧?