zoukankan      html  css  js  c++  java
  • Golang开发命令行工具之flag包的使用

    1、命令行工具概述

    日常命令行操作,相对应的众多命令行工具是提高生产力的必备工具,我在之前的文章我的生产力工具推荐-终端01篇中有推荐过一些我常用的基于terminal终端的命令行cli工具

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

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

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

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

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

    2、flag包介绍

    flag包用来解析命令行参数

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

    flag的详细用法可参考flag包文档

    flag包支持的命令行参数的类型有boolintint64uintuint64float float64stringduration

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

    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的命令,类似linuxmd5sum命令

    其中利用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
    

    Just here,see you ~

    关注公众号加群,更多原创干货与你分享~

  • 相关阅读:
    ajax
    Django之modelform组件
    Django之form组件
    orm事务与锁
    orm之多表操作
    orm之单表操作
    Django之orm
    Django之模板系统
    Django之视图
    hdu5698瞬间移动(杨辉三角+快速幂+逆元)
  • 原文地址:https://www.cnblogs.com/ssgeek/p/15395844.html
Copyright © 2011-2022 走看看