详解Go 创建命令行工具的方法

前言

最近因为项目需要写了一段时间的 Go ,相对于 Java 来说语法简单同时又有着一些 Python 之类的语法糖,让人大呼”真香“。

但现阶段相对来说还是 Python 写的多一些,偶尔还得回炉写点 Java ;自然对 Go 也谈不上多熟悉。

于是便利用周末时间自己做个小项目来加深一些使用经验。于是我便想到了之前利用 Java 写的一个博客小工具。

那段时间正值微博图床大量图片禁止外链,导致许多个人博客中的图片都不能查看。这个工具可以将文章中的图片备份到本地,还能将图片直接替换到其他图床。

我个人现在是一直在使用,通常是在码字的时候利用 iPic 之类的工具将图片上传到微博图床(主要是方便+免费)。写完之后再通过这个工具一键切换到 [SM.MS](http://sm.MS) 这类付费图床,同时也会将图片备份到本地磁盘。

改为用 Go 重写为 cli 工具后使用效果如下:

3-min.gif

需要掌握哪些技能

之所以选择这个工具用 Go 来重写;一个是功能比较简单,但也正好可以利用到 Go 的一些特点,比如网络 IO、协程同步之类。

同时修改为命令行工具后是不是感觉更极客了呢。

再开始之前还是先为不熟悉 GoJavaer 介绍下大概会用到哪些知识点:

  • 使用和管理第三方依赖包(go mod)
  • 协程的运用。
  • 多平台打包。

下面开始具体操作,我觉得即便是没怎么接触过 Go 的朋友看完之后也能快速上手实现一个小工具。

使用和管理第三方依赖

还没有安装 Go 的朋友请参考官网自行安装。

首先介绍一下 Go 的依赖管理,在版本 1.11 之后官方就自带了依赖管理模块,所以在当下最新版 1.15 中已经强烈推荐使用。

它的目的和作用与 Java 中的 mavenPython 中的 pip 类似,但使用起来比 maven 简单许多。

根据它的使用参考,需要首先在项目目录下执行 go mod init 用于初始化一个 go.mod 文件,当然如果你使用的是 GoLang 这样的 IDE,在新建项目时会自动帮我们创建好目录结构,当然也包含 go.mod 这个文件。

在这个文件中我们引入我们需要的第三方包:

module btb

go 1.15

require (
 github.com/cheggaaa/pb/v3 v3.0.5
 github.com/fatih/color v1.10.0
 github.com/urfave/cli/v2 v2.3.0

我这里使用了三个包,分别是:

pb: progress bar,用于在控制台输出进度条。

color: 用于在控制台输出不同颜色的文本。

cli: 命令行工具开发包。

import (
 "btb/constants"
 "btb/service"
 "github.com/urfave/cli/v2"
 "log"
 "os"
)

func main() {
 var model string
 downloadPath := constants.DownloadPath
 markdownPath := constants.MarkdownPath

 app := &cli.App{
 Flags: []cli.Flag{
 &cli.StringFlag{
 Name:  "model",
 Usage:  "operating mode; r:replace, b:backup",
 DefaultText: "b",
 Aliases:  []string{"m"},
 Required: true,
 Destination: &model,
 },
 &cli.StringFlag{
 Name:  "download-path",
 Usage:  "The path where the image is stored",
 Aliases:  []string{"dp"},
 Destination: &downloadPath,
 Required: true,
 Value:  constants.DownloadPath,
 },
 &cli.StringFlag{
 Name:  "markdown-path",
 Usage:  "The path where the markdown file is stored",
 Aliases:  []string{"mp"},
 Destination: &markdownPath,
 Required: true,
 Value:  constants.MarkdownPath,
 },
 },
 Action: func(c *cli.Context) error {
 service.DownLoadPic(markdownPath, downloadPath)

 return nil
 },
 Name: "btb",
 Usage: "Help you backup and replace your blog's images",
 }

 err := app.Run(os.Args)
 if err != nil {
 log.Fatal(err)
 }
}

代码非常简单,无非就是使用了 cli 所提供的 api 创建了几个命令,将用户输入的 -dp-mp 参数映射到 downloadPathmarkdownPath 变量中。

之后便利用这两个数据扫描所有的图片,以及将图片下载到对应的目录中。

更多使用指南可以直接参考官方文档。

可以看到部分语法与 Java 完全不同,比如:

申明变量时类型是放在后边,先定义变量名称;方法参数类似。

类型推导,可以不指定变量类型(新版本的 Java 也支持)

方法支持同时返回多个值,这点非常好用。

公共、私用函数利用首字母大小写来区分。

还有其他的就不一一列举了。


协程

紧接着命令执行处调用了 service.DownLoadPic(markdownPath, downloadPath) 处理业务逻辑。

这里包含的文件扫描、图片下载之类的代码就不分析了;官方 SDK 写的很清楚,也比较简单。

重点看看 Go 里的 goroutime 也就是协程。

我这里使用的场景是每扫描到一个文件就利用一个协程去解析和下载图片,从而可以提高整体的运行效率。

func DownLoadPic(markdownPath, downloadPath string) {
 wg := sync.WaitGroup{}
 allFile, err := util.GetAllFile(markdownPath)
 wg.Add(len(*allFile))

 if err != nil {
 log.Fatal("read file error")
 }

 for _, filePath := range *allFile {

 go func(filePath string) {
 allLine, err := util.ReadFileLine(filePath)
 if err != 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 {
 if err != nil {
  log.Fatal(err)
 }
 err := util.DownloadFile(url, *genFullFileName(downloadPath, filePath, &url))
 if err != nil {
  log.Fatal(err)
 }
 bar.Increment()

 }
 bar.Finish()
 wg.Done()

 }(filePath)
 }
 wg.Wait()
 color.Green("Successful handling of [%v] files.\n", len(*allFile))

 if err != nil {
 log.Fatal(err)
 }
}

就代码使用层面看起来是不是要比 Java 简洁许多,我们不用像 Java 那样需要维护一个 executorService,也不需要考虑这个线程池的大小,一切都交给 Go 自己去调度。

使用时只需要在调用函数之前加上 go 关键字,只不过这里是一个匿名函数。

而且由于 goroutime 非常轻量,与 Java 中的 thread 相比占用非常少的内存,所以我们也不需要精准的控制创建数量。



不过这里也用到了一个和 Java 非常类似的东西:WaitGroup

它的用法与作用都与 Java 中的 CountDownLatch 非常相似;主要用于等待所有的 goroutime 执行完毕,在这里自然是等待所有的图片都下载完毕然后退出程序。

使用起来主要分为三步:

创建和初始化 goruntime 的数量:wg.Add(len(number)

每当一个 goruntime 执行完毕调用 wg.Done() 让计数减一。

最终调用 wg.Wait() 等待WaitGroup 的数量减为0。

对于协程 Go 推荐使用 chanel 来互相通信,这点今后有机会再讨论。

打包

核心逻辑也就这么多,下面来讲讲打包与运行;这点和 Java 的区别就比较大了。

众所周知,Java 有一句名言:write once run anywhere

这是因为有了 JVM 虚拟机,所以我们不管代码最终运行于哪个平台都只需要打出一个包;但 Go 没有虚拟机它是怎么做到在个各平台运行呢。

简单来说 Go 可以针对不同平台打包出不同的二进制文件,这个文件包含了所有运行所需要的依赖,甚至都不需要在目标平台安装 Go 环境。

虽说 Java 最终只需要打一个包,但也得在各个平台安装兼容的 Java 运行环境。

我在这里编写了一个 Makefile 用于执行打包:make relea

# Binary name
BINARY=btb
GOBUILD=go build -ldflags "-s -w" -o ${BINARY}
GOCLEAN=go clean
RMTARGZ=rm -rf *.gz
VERSION=0.0.1

release:
 # Clean
 $(GOCLEAN)
 $(RMTARGZ)
 # Build for mac
 CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GOBUILD)
 tar czvf ${BINARY}-mac64-${VERSION}.tar.gz ./${BINARY}
 # Build for arm
 $(GOCLEAN)
 CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GOBUILD)
 tar czvf ${BINARY}-arm64-${VERSION}.tar.gz ./${BINARY}
 # Build for linux
 $(GOCLEAN)
 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD)
 tar czvf ${BINARY}-linux64-${VERSION}.tar.gz ./${BINARY}
 # Build for win
 $(GOCLEAN)
 CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD).exe
 tar czvf ${BINARY}-win64-${VERSION}.tar.gz ./${BINARY}.exe
 $(GOCLEAN)

可以看到我们只需要在 go build 之前指定系统变量即可打出不同平台的包,比如我们为 Linux 系统的 arm64 架构打包文件:

CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build main.go -o btb

便可以直接在目标平台执行 ./btb  运行程序。

总结

本文所有代码都已上传 Github: https://github.com/crossoverJie/btb

感兴趣的也可以直接运行安装脚本体验。

curl -fsSL https://raw.githubusercontent.com/crossoverJie/btb/master/install.sh | bash

目前这个版本只实现了图片下载备份,后续会完善图床替换及其他功能。



这段时间接触 Go 之后给我的感触颇深,对于年纪 25 岁的 Java 来说,Go 确实是后生可畏,更气人的是还赶上了云原生这个浪潮,就更惹不起了。

一些以前看来不那么重要的小毛病也被重点放大,比如启动慢、占用内存多、语法啰嗦等;不过我依然对这位赏饭吃的祖师爷保持期待,从新版本的 Java 可以看出也在积极改变,更不用说它还有无人撼动的庞大生态。

到此这篇关于详解Go 创建命令行工具的方法的文章就介绍到这了,更多相关Go 命令行工具内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go语言中使用flag包对命令行进行参数解析的方法

    flag flag 是Go 标准库提供的解析命令行参数的包. 使用方式: flag.Type(name, defValue, usage) 其中Type为String, Int, Bool等:并返回一个相应类型的指针. flag.TypeVar(&flagvar, name, defValue, usage) 将flag绑定到一个变量上. 自定义flag 只要实现flag.Value接口即可: type Value interface { String() string Set(string)

  • mongodb 命令行下及php中insert数据详解

    前面说了到数据库连接操作,请参考:mongodb 添加用户及权限设置详解 对数据库的操作:请参考:mongodb 数据库操作详解--创建,切换,删除 下面说一下,数据库表的插入操作 1,命令行下的insert操作 > use test; #切换到test数据库 switched to db test > document=({"title" : "linux命令", "auther" : "tank" }); #定

  • Go语言命令行操作命令详细介绍

    Go 命令 Go语言自带有一套完整的命令操作工具,你可以通过在命令行中执行go来查看它们: 图1.3 Go命令显示详细的信息 这些命令对于我们平时编写的代码非常有用,接下来就让我们了解一些常用的命令. go build 这个命令主要用于测试编译.在包的编译过程中,若有必要,会同时编译与之相关联的包. 1.如果是普通包,就像我们在1.2节中编写的mymath包那样,当你执行go build之后,它不会产生任何文件.如果你需要在$GOPATH/pkg下生成相应的文件,那就得执行go install了

  • 使用Django启动命令行及执行脚本的方法

    使用django启动命令行和脚本,可以方便的使用django框架做开发,例如,数据库的操作等. 下面分别介绍使用方法. django shell的启动 启动命令: $/data/python-virtualenv/apple/bin/python /data/example/apple/manage.py shell 与普通的python 命令行有什么区别? django shell 带有django的配置信息,可以使用django的框架.例如,定义model为Apples,可以直接使用Appl

  • go语言执行windows下命令行的方法

    本文实例讲述了go语言执行windows下命令行的方法.分享给大家供大家参考.具体如下: 在golang里执行windows下的命令行,例如在golang里面调用 del d:\a.txt 命令 复制代码 代码如下: package main import(     "fmt"     "os/exec" ) func main(){       c := exec.Command("cmd", "/C", "del

  • Golang命令行进行debug调试操作

    GoLang调试工具Delve 1.先获取呗: go get -u github.com/derekparker/delve/cmd/dlv 2.编写测试代码呗: func main(){ http.HandleFunc("/test",func(writer http.ResponseWriter,req *http.Request){ //TODO }) log.Fatal(http.ListenAndServe("127.0.0.1:8080",nil)) }

  • 详解Go 创建命令行工具的方法

    前言 最近因为项目需要写了一段时间的 Go ,相对于 Java 来说语法简单同时又有着一些 Python 之类的语法糖,让人大呼"真香". 但现阶段相对来说还是 Python 写的多一些,偶尔还得回炉写点 Java :自然对 Go 也谈不上多熟悉. 于是便利用周末时间自己做个小项目来加深一些使用经验.于是我便想到了之前利用 Java 写的一个博客小工具. 那段时间正值微博图床大量图片禁止外链,导致许多个人博客中的图片都不能查看.这个工具可以将文章中的图片备份到本地,还能将图片直接替换到

  • 如何使用Swift来实现一个命令行工具的方法

    本文即简单介绍了如何在Swift中开发命令行工具,以及与Shell命令的交互.水文一篇,不喜勿喷. 主要是使用该工具来解析微信的性能监控组件Matrix的OOM Log. 基本模块 这里,仅简单介绍了常见的基本模块. Process Process类可以用来打开另外一个子进程,并监控其运行情况. launchPath:指定了执行路径.如可以设置为 /usr/bin/env ,这个命令可以用于打印本机上所有的环境变量:也可以用于执行shell命令,如果你接了参数的话.本文的Demo就用它来执行输入

  • Rust实现grep命令行工具的方法

    目录 一.功能: 二.介绍 2.1 使用到的知识: 2.2 代码 三.使用 一.功能: 1.输入要查询的字符串和文件名,输出所有匹配的行的内容 2.如果设置环境变量IGNORE_CASE,则grep匹配将忽略大小写 3.可使用 > 符号来重定向标准输出到指定文件中 二.介绍 2.1 使用到的知识: 读取命令行参数 读取文件内容 错误处理 Test Driven Development(TDD) 使用环境变量控制不同行为 2.2 代码 1.main.rs use std::env; use std

  • 详解Vue使用命令行搭建单页面应用

    使用命令行搭建单页面应用 我们来看一下最后完成的效果: 大纲 1. 下载 node, git, npm 2. 使用命令行安装一个项目 一. 下载工具 node, git 的下载大家可以去官网自行下载就可以了. 下面说一说安装 npm,npm 是在以上两个都安装的情况下才可以的. 国内直接 git npm 非常慢,所以我们到 taobao 的 npm 镜像下面去下载安装 镜像地址>> 打开安装好的 git bash 1 . 在 Git Bash 输入镜像的地址: 下载成功后,输入 cnpm -v

  • 详解Node.js 命令行程序开发教程

    一种编程语言是否易用,很大程度上,取决于开发命令行程序的能力. Node.js 作为目前最热门的开发工具之一,怎样使用它开发命令行程序,是 Web 开发者应该掌握的技能. 下面就是我在它的基础上扩展的教程,应该是目前最好的解决方案了. 一.可执行脚本 我们从最简单的讲起. 首先,使用 JavaScript 语言,写一个可执行脚本 hello . #!/usr/bin/env node console.log('hello world'); 然后,修改 hello 的权限. $ chmod 755

  • 详解用Node.js写一个简单的命令行工具

    本文介绍了用Node.js写一个简单的命令行工具,分享给大家,具体如下: 操作系统需要为Linux 1. 目标 在命令行输入自己写的命令,完成目标任务 命令行要求全局有效 命令行要求可以删除 命令行作用,生成一个文件,显示当前的日期 2. 代码部分 新建一个文件,命名为sherryFile 文件sherryFile的内容 介绍: 生成一个文件,文件内容为当前日期和创建者 #! /usr/bin/env node console.log('command start'); const fs = r

  • 使用node打造自己的命令行工具方法教程

    一.实现一个简单的功能 二.环境 1.系统: window 10 2.编辑器: vscode 3.node版本: 8.7.0 三.开始玩 1.打开命令行,新建一个pa'ckage.json npm init 这时看到一个新的package.json生成了,使用编辑器打开 2.修改package.json,新增一个bin属性 { "name": "my-cli", "version": "1.0.0", "descri

  • 使用Python编写类UNIX系统的命令行工具的教程

    引言 您是否能编写命令行工具?也许您可以,但您能编写出真正好用的命令行工具吗?本文讨论使用 Python 来创建一个强健的命令行工具,并带有内置的帮助菜单.错误处理和选项处理.由于一些奇怪的原因,很多人并不了解 Python? 的标准库具有制作功能极其强大的 *NIX 命令行工具所需的全部工具. 可以这样说,Python 是制作 *NIX 命令行工具的最佳语言,因为它依照"batteries-included"的哲学方式工作,并且强调提供可读性高的代码.但仅作为提醒,当您发现使用 Py

  • 在vbs运行命令行工具后让命令窗口保持打开状态的脚本

    问: 您好,脚本专家!如何在运行像 Ping 或 Ipconfig 这样的工具后让命令窗口保持打开状态? -- DB 答: 您好,DB.这个问题让我们想起了往事.有一个脚本专家刚来 Microsoft,那时许多人认为 WMI 和 ADSI 对于脚本编写者来说太难使用.因此,人们建议这个脚本专家不使用 WMI 或 ADSI,而是干脆使用 VBScript 作为调用命令行工具的方法.事实上,这个脚本专家编写的第一章就是一个关于事件日志管理的章节,该章后来成为 Microsoft Windows 20

  • python命令行工具Click快速掌握

    前言 写 Python 的经常要写一些命令行工具,虽然标准库提供有命令行解析工具 Argparse,但是写起来非常麻烦,我很少会使用它.命令行工具中用起来最爽的就是 Click,它是 Flask 的团队 pallets 的开源项目.Click 只要很少的代码就可以优雅地创造一个命令行工具,它致力于将创建命令行工具的过程变的快速而有趣. 百闻不如一试 安装 pip install Click 使用 创建 click_demo.py ,写一个最简单的函数 import click @click.co

随机推荐