Golang开发命令行之flag包的使用方法
作者:山山仙人博客 发布时间:2024-02-16 09:26:04
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
来源:https://www.tuicool.com/articles/Yj67Brr


猜你喜欢
- 以此文记录Python与Tensorflow及其开发环境的安装与配置过程,以备以后参考。1 硬件与系统条件 Win7 64位系统,显卡为NV
- 如何在页面中对不同的数据进行相同的处理?selectId.asp' 列出所有客户的客户名称<html><
- 一、前言传统上,开发人员在 JavaScript 类中为实例中可能需要的任何数据创建属性。对于在构造函数中随时可用的小块数据来说,这不是问题
- 简单展示如何利用python中的pandas库创建、读取、修改CSV数据文件1 写入CSV文件import numpy as npimpor
- 1.3 安装 ASP.net跟基督山一起检查你们的计算机哦CPU Pentium II 450以上,推荐733内存 256M 推荐 512M
- torch.nn.Modules 相当于是对网络某种层的封装,包括网络结构以及网络参数和一些操作torch.nn.Module 是所有神经网
- 下面把角色分为两种,普通用户和管理员用户,至少对于普通用户来说,直接修改DB是不可取的,要有用户注册的功能,下面就开始进行用户注册的开发。用
- 加班时抽空弄的,javascript图片链接定时轮换,自适应图片大小,支持预载,进行了简单封装,方便调用。发现自己还是菜得很,一个简单效果被
- SPI是一种JDK提供的加载插件的灵活机制,分离了接口与实现,就拿常用的数据库驱动来说,我们只需要在spring系统中引入对应的数据库依赖包
- 微信小程序中使用地图(map)组件,通过点击(tap)获取经纬度,按照官方的回应,暂时是没法做到的,从地图组件API多有残缺判断,怀疑是个实
- 把你想加密的文档的源代码拷贝到下面的文本框内,按下生成按钮就可以得到一段加密了的页面代码,把代码复制到新的页面就可以了. [注意:可重复加密
- 数据准备moduls.py# 构建表结构from django.db import models# 表app01_publishclass
- 前言对于会PhotoShop的人来说,抠图是非常简单的操作了,有时候几秒钟就能扣好一张图。不过一些比较复杂的图,有时候还是要画点时间的,今天
- 1、添加依赖<dependency><groupId>com.baomidou</groupId><
- salt分发后,主动将已完成的任务数据推送到redis中,使用redis的生产者模式,进行消息传送#coding=utf-8import f
- 目录函数什么是函数/方法2.为什么需要函数1、载体2、组织3、复用4、封装5、清晰6、按需3.如何声明/调用一个函数4.函数/方法的参数1、
- 废话不多说,直接上代码/** * lhgcalendar时间插件限制只能选择三个月 * @d 获取到的开始时间 * @m 要限制的时间的长度
- css+div做的菜单:一个主显示层,别的列表都隐藏着,用js函数设置列表的显示和隐藏。分别用到了两个函数,函数实现的效果是一样的,一个是参
- URLURL 是统一资源定位符,对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有
- 简介这两天更新完Xcode8之后发现Xcode对图标的要求又有了变化,之前用的一个小应用“IconKit”还没赶上节奏,已经不能满足Xcod