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

十分钟学会用Go写命令行工具

时间:2023-03-17 19:12:50 科技观察

前言最近因为项目需要写Go语言一段时间,与Java相比,语法简单而且像Python一样有一些语法糖,使得人们大呼“真香”。但是现阶段,相对来说还是用Python写的比较多,偶尔也得回去写一些Java;我自然对Go不是很熟悉。于是利用周末的时间做了一个小项目,加深了一些经验。于是想到了之前用Java写的博客小工具。当时微博图床大量图片禁止外链,导致很多个人博客的图片无法查看。本工具可以将文章中的图片备份到本地,可以直接替换图片到其他图床。个人目前一直在用,一般打码的时候用iPic之类的工具上传图片到微博图床(主要是方便+免费)。写好后用本工具一键切换到[SM.MS](http://sm.MS)等付费图床,图片也会备份到本地磁盘。改写为Go中的cli工具后,效果如下:需要掌握哪些技能?之所以选择这个工具用Go来重写,是因为功能比较简单,但是也可以利用Go的一些特性,比如网络IO,Coroutine同步等等。同时,改成命令行工具后是不是觉得更geek了?在重新开始之前,先介绍一下不熟悉Go的Javaer可能会用到的知识点:第三方依赖包(gomod)协程的使用和管理。多平台打包。下面开始具体操作。我想即使是没有接触过Go的朋友,看完也能快速上手,实现一个小工具。使用和管理第三方依赖如果你还没有安装Go,请参考官网自行安装。首先介绍下Go的依赖管理。1.11版本后,官方自带依赖管理模块,强烈推荐在最新的1.15版本使用。其用途和功能类似于Java中的maven和Python中的pip,但使用起来比maven简单得多。根据其使用参考,需要在项目目录下执行gomodinit,初始化一个go.mod文件。当然,如果你使用像GoLang这样的IDE,它会在新建项目的时候自动为我们创建一个目录结构。当然还包括go.mod文件。在这个文件中我们引入了我们需要的第三方包:modulebtbgo1.15require(github.com/cheggaaa/pb/v3v3.0.5github.com/fatih/colorv1.10.0github.com/urfave/cli/v2v2.3.0)I这里用到了三个包,分别是:pb:progressbar,用于在控制台输出进度条。color:用于在控制台输出不同颜色的文本。cli:命令行工具开发包。import("btb/constants""btb/service""github.com/urfave/cli/v2""log""os")funcmain(){varmodelstringdownloadPath:=constants.DownloadPathmarkdownPath:=constants.MarkdownPathapp:=&cli.App{Flags:[]cli.Flag{&cli.StringFlag{Name:"model",Usage:"operatingmode;r:replace,b:backup",DefaultText:"b",Aliases:[]string{"m"},必填:true,目标:&model,},&cli.StringFlag{名称:“下载路径”,用法:“图像存储的路径”,别名:[]string{“dp”},目标:&downloadPath,必填:true,值:常量.DownloadPath,},&cli.StringFlag{Name:"markdown-path",Usage:"Thepathwherethemarkdownfileisstored",Aliases:[]string{"mp"},Destination:&markdownPath,Required:true,Value:constants.MarkdownPath,},},Action:func(c*cli.Context)error{service.DownLoadPic(markdownPath,downloadPath)returnnil},名称:"btb",Usage:"Helpyoubackupandreplaceyourblog'simages",}err:=app.Run(os.Args)iferr!=nil{log.Fatal(err)}}代码很简单,无非就是使用cli提供的api创建了几个命令,将用户输入的-dp和-mp参数映射到downloadPath和markdownPath变量,然后利用这两个数据扫描所有图片,将图片下载到对应目录下。更多使用指导你可以直接参考官方文档,可以看到有些语法和Java完全不一样,比如:声明变量时,类型放在最后,先定义变量名;方法参数类似,类型推导,可以不指定变量类型(Java新版本也支持)方法支持同时返回多个值,很好用,public函数和private函数是通过首字母大小写来区分的。还有其他没有列出的一个。协程在命令执行后立即调用服务.DownLoadPic(markdownPath,downloadPath)处理业务逻辑。此处包含的文件扫描、图片下载等代码不做分析;官方SDK写的很清楚,也比较简单。重点说说Go中的goroutine,也就是coroutine我这里使用的场景是每次扫描一个文件的时候用一个coroutine来解析下载图片,这样可以提高整体的运行效率。funcDownLoadPic(markdownPath,downloadPathstring){wg:=sync.WaitGroup{}allFile,err:=util.GetAllFile(markdownPath)wg.Add(len(*allFile))iferr!=nil{log.Fatal("readfileerror")}for_,filePath:=range*allFile{gofunc(filePathstring){allLine,err:=util.ReadFileLine(filePath)iferr!=nil{log.Fatal(err)}availableImgs:=util.MatchAvailableImg(allLine)bar:=pb.ProgressBarTemplate(constants.PbTmpl).Start(len(*availableImgs))bar.Set("fileName",filePath).SetWidth(120)for_,url:=range*availableImgs{iferr!=nil{log.Fatal(err)}err:=util.DownloadFile(url,*genFullFileName(downloadPath,filePath,&url))iferr!=nil{log.Fatal(err)}bar.Increment()}bar.Finish()wg.Done()}(filePath)}wg.Wait()color.Green("Successfulhandlingof[%v]files.\n",len(*allFile))iferr!=nil{log.Fatal(err)}}它看起来像代码使用是不是比Java简单多了?我们不需要像Java那样维护一个executorService,也不需要考虑线程池的大小。一切都留给Go进行调度。使用时只需要在函数调用前加上go关键字即可,不过这里是匿名函数。并且由于goroutines非常轻量级,与Java中的线程相比占用的内存非常少,所以我们不需要精确控制创建次数。不过这里也用到了和Java很相似的东西:WaitGroup。它的用法和作用与Java中的CountDownLatch非常相似;它主要用来等待所有的goroutine执行完毕。这里,自然是要等所有图片下载完毕,然后退出程序。主要分三步使用:创建并初始化goruntime的个数:wg.Add(len(number)每执行一个goruntime就调用wg.Done()将count减一。最后调用wg.Wait()等待WaitGroup数减少为0,对于协程Go,建议使用chanel相互通信,这点后面会讲到,打包的核心逻辑就这么多,先说打包和运行在下面;这和Java有很大的不同众所周知,Java有一句??名言:writeoncerunanywhere这是因为JVM虚拟机的缘故,所以无论代码在哪个平台,我们只需要打出一个包最终运行在;但是Go没有虚拟机,它是如何做到在各个平台运行的。简单的说,Go可以为不同的平台打包不同的二进制文件,这个文件包含了所有运行需要的依赖,还有无需安装Go环境e目标平台。虽然Java最后只需要打包,但是每个平台都必须安装一个兼容的Java运行环境。我这里写了一个Makefile来打包:makerelease#BinarynameBINARY=btbGOBUILD=gobuild-ldflags"-s-w"-o${BINARY}GOCLEAN=gocleanRMTARGZ=rm-rf*.gzVERSION=0.0.1release:#Clean$(GOCLEAN)$(RMTARGZ)#BuildformacCGO_ENABLED=0GOOS=darwinGOARCH=amd64$(GOBUILD)tarczvf${BINARY}-mac64-${VERSION}.tar.gz./${BINARY}#Buildforarm$(GOCLEAN)CGO_ENABLED=0GOOS=linuxGOARCH=arm64$(GOBUILD)tarczvf${BINARY}-arm64-${VERSION}.tar.gz./${BINARY}#Buildforlinux$(GOCLEAN)CGO_ENABLED=0GOOS=linuxGOARCH=amd64$(GOBUILD)tarczvf${BINARY}-linux64-${VERSION}.tar.gz./${BINARY}#Buildforwin$(GOCLEAN)CGO_ENABLED=0GOOS=windowsGOARCH=amd64$(GOBUILD).exetarczvf${BINARY}-win64-${VERSION}。tar.gz./${BINARY}.exe$(GOCLEAN)可以看到我们只需要在gobuild之前指定系统变量就可以创建不同平台的包。比如我们针对Linux系统的arm64架构打包文件:CGO_ENABLED=0GOOS=linuxGOARCH=arm64gobuildmain.go-obtb可以直接在目标平台上执行./btb运行程序。综上所述,本文所有代码均已上传到Github:https://github.com/crossoverJie/btb有兴趣的也可以直接运行安装脚本体验。curl-fsSLhttps://raw.githubusercontent.com/crossoverJie/btb/master/install.sh|bash目前该版本仅实现图片下载和备份,后续会完善图床更换等功能。这段时间接触围棋后,感触很深。对于一个25岁的Java来说,Go确实是一个令人敬畏的年轻人。一些以前看起来不那么重要的小问题也被放大了,比如启动慢、内存占用大、语法冗长等;不过我还是很期待这个享受美食的元老,从新版Java就可以看出来。它在积极变化,更何况它还有一个无人能撼动的庞大生态。