目录

1、命令行工具概述

日常命令行操作,相对应的众多命令行工具是提高生产力的必备工具,鼠标能够让用户更容易上手,降低用户学习成本。 而对于开发者,键盘操作模式能显著提升生产力,还有在一些专业工具中, 大量使用快捷键代替繁琐的鼠标操作,能够使开发人员更加专注于工作,提高效率,因为键盘操作模式更容易产生肌肉记忆

举个栗子:我司业务研发,前些年在我们的强力推动下(被迫)转向使用了 git 作为版本控制,开始使用的是图形化“小乌龟”工具。后续出现几次问题解决起来较麻烦后,推荐其使用原生的 git 命令行。如今,使用 git 命令行操作版本控制可谓 “一顿操作猛如虎……”

命令行(键盘)操作在很大程度上可以提高工作效率,与之相对应的是鼠标(触屏等)操作,这两种模式是目前的主流人机交互方式

设计一款命令行工具的开发语言可以选择原始的 shell 、甚至是更原始的语言 c ,更为容易上手且功能更多的有 node python golang

本文是基于 golang 开发命令行工具的开篇,主要是基于 golang 原生内置的、轻量的 flag 包实现,用 golang 设计命令行工具而不用 shell python 的原因这里就不做论述了

2、flag包介绍

flag 包用来解析命令行参数

相比简单的使用 os.args 来获取命令行参数, flag 可以实现按照更为通用的命令行用法,例如 mysql -u root -p 123456 。其中 mysql 是命令行的名称即这个命令, -u-p 分别是这个命令的两个参数:用户名和密码,后面接着的是对应的参数值,有了参数的声明之后,两个参数可以互换位置,参数值也可以选填或按照缺省(默认)值进行指定

flag 包支持的命令行参数的类型有 bool int int64 uint uint64 float float64 string duration

即布尔值、整型、浮点型、字符串、时间段类型

3、flag包命令行参数的定义

定义 flag 命令行参数,用来接收命令行输入的参数值,一般有以下两种方法

flag.typevar():先定义参数(实际上是指针),再定义 flag.typevar 将命令行参数存储(绑定)到前面参数的值的指针(地址)

var name string
var age int
var height float64
var graduated bool
// &name 就是接收用户命令行中输入的-n后面的参数值
// 返回值是一个用来存储name参数的值的指针/地址
// 定义string类型命令行参数name,括号中依次是变量名、flag参数名、默认值、参数说明
flag.stringvar(&name, "n", "", "name参数,默认为空")
// 定义整型命令行参数age
flag.intvar(&age,"a", 0, "age参数,默认为0")
// 定义浮点型命令行参数height
flag.float64var(&height,"h", 0, "height参数,默认为0")
// 定义布尔型命令行参数graduated
flag.boolvar(&graduated,"g", false, "graduated参数,默认为false")


flag.type():用短变量声明的方式定义参数类型及变量名

// 定义string类型命令行参数name,括号中依次是flag参数名、默认值、参数说明
nameptr := flag.string("n", "", "name参数,默认为空")
// 定义整型命令行参数age
age := flag.int("a", 0, "age参数,默认为0")
// 定义浮点型命令行参数height
height := flag.float64("h", 0, "height参数,默认为0")
// 定义布尔型命令行参数graduated
graduated:= flag.bool("g", false, "graduated参数,默认为false")

4、flag包命令行参数解析

固定用法,定义好参数后,通过调用 flag.parse() 来对命令行参数进行解析写入注册的 flag 里,进而解析获取参数值,通过查看源码中也是调用的 os.args

源码路径 go/src/flag/flag.go

// parse parses the command-line flags from os.args[1:]. must be called
// after all flags are defined and before flags are accessed by the program.
func parse() {
 // ignore errors; commandline is set for exitonerror.
 commandline.parse(os.args[1:])
}

进而查看 parse 方法的源码

func (f *flagset) parse(arguments []string) error {
 f.parsed = true
 f.args = arguments
 for {
  seen, err := f.parseone()
  if seen {
   continue
  }
  if err == nil {
   break
  }
  switch f.errorhandling {
  case continueonerror:
   return err
  case exitonerror:
   if err == errhelp {
    os.exit(0)
   }
   os.exit(2)
  case paniconerror:
   panic(err)
  }
 }
 return nil
}

真正解析参数的是 parseone 方法(这里省略源码),结论是

  • 当遇到单独的一个 “-” 或不是 “-” 开始时,会停止解析
  • 遇到连续的两个 “-” 时,解析停止
  • 在终止符”-“之后停止

解析参数时,对于参数的指定方式一般有”-“、”–“、以及是否空格等方式,组合下来有如下几种方式

-flag xxx 空格和一个 – 符号
–flag xxx 空格和两个 – 符号
-flag=xxx 等号和一个 – 符号
–flag=xxx 等号和两个 – 符号

其中, -flag xxx 方式最为常用,如果参数是布尔型,只能用等号方式指定

5、flag包命令行帮助

flag 包默认会根据定义的命令行参数,在使用时如果不输入参数就打印对应的帮助信息

这样的帮助信息我们可以对其进行覆盖去改变默认的 usage

package main

import (
    "flag"
    "fmt"
)

func main()  {
    var host string
    var port int
    var verbor bool
    var help bool
    // 绑定命令行参数与变量关系
    flag.stringvar(&host, "h", "127.0.0.1", "ssh host")
    flag.intvar(&port, "p", 22, "ssh port")
    flag.boolvar(&verbor, "v", false, "detail log")
    flag.boolvar(&help, "h", false, "help")
    // 自定义-h
    flag.usage = func() {
        fmt.println(`
usage: flag [-h addr] [-p port] [-v]

options: 
    `)
        flag.printdefaults()
    }
    // 解析命令行参数
    flag.parse()
    if help {
        flag.usage()
    } else {
        fmt.println(host, port, verbor)
    }
}
/*
  go run flag_args.go -h

usage: flag [-h addr] [-p port] [-v]

options:

  -h string
        ssh host (default "127.0.0.1")
  -p int
        ssh port (default 22)
  -h    help
  -v    detail log
 */

6、flag定义短参数和长参数

简单来说,短参数和长参数,就是例如我们在使用某些命令时,查看命令版本可以输入 -v ,也可以输入 --version 。这种情况下, flag 并没有默认支持,但是可以通过可以两个选项共享同一个变量来实现,即通过给某个相同的变量设置不同的选项,参数在初始化的时候其顺序是不固定的,因此还需要保证其拥有相同的默认值

package main

import (
  "fmt"
  "flag"
)

var loglevel string

func init() {
  const (
    defaultloglevel = "debug"
    usage = "set log level"
  )
  flag.stringvar(&loglevel, "log_level", defaultloglevel, usage)
  flag.stringvar(&loglevel, "l", defaultloglevel, usage + "(shorthand)")
}

func main() {
  flag.parse()
  fmt.println("log level:", loglevel)
}

通过 const 声明公共的常量,并在默认值以及帮助信息中去使用,这样就可以实现了

7、示例

实现计算字符串或目录下递归计算文件 md5 的命令,类似 linux md5sum 命令

其中利用 bufio 分批次读取文件,防止文件过大时造成资源占用高

package main

import (
 "bufio"
 "crypto/md5"
 "flag"
 "fmt"
 "io"
 "os"
 "strings"
)

func md5reader(reader *bufio.reader) string {  //
 hasher := md5.new()  // 定义md5 hash计算器
 bytes := make([]byte, 1024*1024*10)  // 分批次读取文件

 for {
  n, err := reader.read(bytes)
  if err != nil {
   if err != io.eof {
    return ""
   }
   break
  } else {
   hasher.write(bytes[:n])
  }
 }
 return fmt.sprintf("%x", hasher.sum(nil))
}

func md5file(path string) (string, error) {
 file, err := os.open(path)
 if err != nil {
  return "", err
 } else {
  defer file.close()
  return md5reader(bufio.newreader(file)), nil
 }
}

func md5str(txt string) (string, error) {
 return md5reader(bufio.newreader(strings.newreader(txt))), nil
 //return fmt.sprintf("%x", md5.sum([]byte(txt)))
}

func main()  {
 txt := flag.string("s", "", "md5 txt")
 path := flag.string("f", "", "file path")
 help := flag.bool("h", false, "help")
 flag.usage = func() {
  fmt.println(`
usage: md5 [-s 123abc] [-f path]
options:
  `)
  flag.printdefaults()
 }
 flag.parse()
 if *help || *txt == "" && *path == "" {
  flag.usage()
 } else {
  var md5 string
  var err error
  if *path != "" {
   md5, err = md5file(*path)
  } else {
   md5, err = md5str(*txt)
  }
  if err != nil {
   fmt.println(err)
  } else {
   fmt.println(md5)
  }
 }
}

编译生成二进制文件

  go build -o md5go -x md5_bufio.go
  ll md5go 
-rwxr-xr-x  1 ssgeek  staff   1.9m oct 2 00:54 md5go


测试使用

  ./md5go -h             

usage: md5 [-s 123abc] [-f path]
options:
                
  -f string
        file path
  -h    help
  -s string
        md5 txt
  ./md5go -s 123456
e10adc3949ba59abbe56e057f20f883e
  ./md5go -f md5_bufio.go
8607a07cbb98cec0e9abe14b0db0bee6

到此这篇关于golang开发命令行之flag包的使用方法的文章就介绍到这了,更多相关golang开发命令行之flag包的使用内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!