Go-CLI 命令行实用程序
来源:互联网 发布:中银国际期货软件 编辑:程序博客网 时间:2024/05/02 00:41
Go语言实现CLI命令行
一. 概述
CLI(Command Line Interface)实用程序是Linux下应用开发的基础。正确的编写命令行程序让应用与操作系统融为一体,通过shell或script使得应用获得最大的灵活性与开发效率。Linux提供了cat、ls、copy等命令与操作系统交互;go语言提供一组实用程序完成从编码、编译、库管理、产品发布全过程支持;容器服务如docker、k8s提供了大量实用程序支撑云服务的开发、部署、监控、访问等管理任务;git、npm等都是大家比较熟悉的工具。尽管操作系统与应用系统服务可视化、图形化,但在开发领域,CLI在编程、调试、运维、管理中提供了图形化程序不可替代的灵活性与效率。
二. 具体要求
实现指令的要求请点击这里
三. 实现
1. 引用到的包
import ( "fmt" "flag" "os" "io/ioutil" "io" "bufio" "os/exec")
2. 数据结构(指令结构)
type selpg_args struct{ start_page int //开始操作的页编号 end_page int //结束操作的页编号 in_filename string //读取的文件名 page_len int //读取的文本页的数量 page_type bool //操作的类型,-lNumber指这表明页有固定长度,每页为Nnumber行,-f在输入中寻找换页符,并将其作为页定界符处理。 print_dest string //输出文件名}
3. main函数
func main(){ var args selpg_args //命令行参数 get_args(&args) //读取命令行参数 check_args(&args) //检查参数是否符合要求 run(&args) //运行该命令}
4. 获取参数
func get_args(parg *selpg_args) {
flag.IntVar(&(parg.start_page), “s”, -1, “startPage”)
flag.IntVar(&(parg.end_page), “e”, -1, “endPage”)
flag.IntVar(&(parg.page_len), “l”, 72, “pageLength”)
flag.StringVar(&(parg.print_dest), “d”, “”, “printDest”)
flag.BoolVar(&(parg.page_type), “f”, false, “pageType”)
flag.Parse()
args_left := flag.Args()
if(len(args_left) > 0){
parg.in_filename = string(args_left[0])
} else {
parg.in_filename = “”
}
}
flag是go语言用来解析命令行参数的包,具有很强的功能,定义flags有两种方式:
1)flag.Xxx(),其中Xxx可以是Int、String等;返回一个相应类型的指针,如:
var ip = flag.Int("flagname", 1234, "help message for flagname")
2)flag.XxxVar(),将flag绑定到一个变量上,如:
var flagvar intflag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
另外,还可以创建自定义flag,只要实现flag.Value接口即可(要求receiver是指针),这时候可以通过如下方式定义该flag:
flag.Var(&flagVal, "name", "help message for flagname")
获得flag参数后,要用flag.Parse()
函数才能把参数解析出来,这里还有一个神奇的东西就是,解析完指定的参数后,还可以通过调用args_left := flag.Args()
来获得未定义但输入了的参数
5. 检查参数是否符合要求
func check_args(parg *selpg_args) { if parg == nil{ fmt.Fprintf(os.Stderr, "\n[Error]The args is nil!Please check your program!\n\n") os.Exit(1) }else if(parg.start_page == -1) || (parg.end_page == -1){ fmt.Fprintf(os.Stderr, "\n[Error]The startPage and endPage is not allowed empty!Please check your command!\n\n") os.Exit(2) }else if (parg.start_page < 0) || (parg.end_page < 0){ fmt.Fprintf(os.Stderr, "\n[Error]The startPage and endPage is not negative!Please check your command!\n\n") os.Exit(3) }else if parg.start_page > parg.end_page{ fmt.Fprintf(os.Stderr, "\n[Error]The startPage can not be bigger than the endPage!Please check your command!\n\n") os.Exit(4) }else{ pt := 'f' if parg.page_type == false { pt = 'l' } fmt.Printf("\n[ArgsStart]\n") fmt.Printf("startPage: %d\nendPage: %d\ninputFile: %s\npageLength: %d\npageType: %c\nprintDestation: %s\n[ArgsEnd]\n\n", parg.start_page, parg.end_page, parg.in_filename, parg.page_len, pt, parg.print_dest) }}
这里主要是比较起始页和终止页之间的逻辑关系,没有什么难度。这里要特别说明的是这个pt := 'f'
和pt = 'l'
的问题,因为这里用了一个Bool值来判断是两种类型中的哪一个。
6. 运行指令
func run(parg *selpg_args) { var fin *os.File var fout *os.File var fout_d io.WriteCloser if parg.in_filename == ""{ fin = os.Stdin } else { check_file_access(parg.in_filename) var err_fin error fin, err_fin = os.Open(parg.in_filename) check_err(err_fin, "fileInput") } fout, fout_d = check_fout(parg.print_dest) if(fout != nil){ output_to_file(fout, fin, parg.start_page, parg.end_page, parg.page_len) } else { output_to_exc(fout_d, fin, parg.start_page, parg.end_page, parg.page_len) }}
这个模块是这次的核心内容,主要涉及文件的读写问题,下面分模块介绍
(1)输入文件检查
func check_file_access(filename string) { _, err_o := os.Stat(filename) if os.IsNotExist(err_o){ fmt.Fprintf(os.Stderr, "\n[Error]: input file \"%s\" does not exist\n\n",filename); os.Exit(5); } _, err_r := ioutil.ReadFile(filename) check_err(err_r, filename)}
文件的读写使用到了go语言的os包,这个包将文件检测,读写和输入输出流封装起来。
os.Stat()
这个函数是获取文件的信息,函数函数的原型func Stat(name string) (fi FileInfo, err error
)输出是文件的名称返回一个FileInfo的接口和err信息
os.IsNotExist(err_o)
检测文件是否存在
os.Exit(5)
强制终止命令的执行
(2)错误检查
func check_err(err error, object string) { if err != nil{ fmt.Fprintf(os.Stderr, "\n[Error]%s:",object); panic(err) }}
这里主要介绍panic函数,当出现error(即error != nil)时,panic(error)
会停止命令的执行,并将错误原因转成字符串打印出来,是个检测错误的实用函数,要掌握
(3)输出文件检查
unc check_fout(printDest string) (*os.File, io.WriteCloser) { var fout *os.File var fout_d io.WriteCloser if len(printDest) == 0 { fout = os.Stdout; fout_d = nil } else { fout = nil var err_dest error cmd := exec.Command("./" + printDest) fout_d, err_dest = cmd.StdinPipe() check_err(err_dest, "fout_dest") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err_start := cmd.Start() check_err(err_start,"command-start") } return fout, fout_d}
这里的逻辑是,检查是否存在输出文件名,存在,则将输入的内容由管道送至其它命令(如将输出在打印机上打印),如果不存在,则将数据打印到屏幕(即标准输出os.Stdout
)
这里有个难点,就是exec
这个东西,跟go语言的os/exec包有关,exec包执行外部命令,它将os.StartProcess进行包装使得它更容易映射到stdin和stdout,并且利用pipe连接i/o
type Cmd struct { Path string //运行命令的路径,绝对路径或者相对路径 Args []string // 命令参数 Env []string //进程环境,如果环境为空,则使用当前进程的环境 Dir string //指定command的工作目录,如果dir为空,则comman在调用进程所在当前目录中运行 Stdin io.Reader //标准输入,如果stdin是nil的话,进程从null device中读取(os.DevNull),stdin也可以时一个文件,否则的话则在运行过程中再开一个goroutine去 //读取标准输入 Stdout io.Writer //标准输出 Stderr io.Writer //错误输出,如果这两个(Stdout和Stderr)为空的话,则command运行时将响应的文件描述符连接到os.DevNull ExtraFiles []*os.File SysProcAttr *syscall.SysProcAttr Process *os.Process //Process是底层进程,只启动一次 ProcessState *os.ProcessState //ProcessState包含一个退出进程的信息,当进程调用Wait或者Run时便会产生该信息. }
(4)将输入内容打印到屏幕
func output_to_file(fout *os.File, fin *os.File, pageStart int, pageEnd int, pageLen int) { line_ctr := 0 page_ctr := 1 buf := bufio.NewReader(fin) for true { line,err := buf.ReadString('\n') if err == io.EOF{ break } check_err(err, "file_in_out") line_ctr++ if line_ctr > pageLen{ page_ctr++ line_ctr = 1 } if (page_ctr >= pageStart) && (page_ctr <= pageEnd){ fmt.Fprintf(fout, "%s", line) } } if page_ctr < pageStart { fmt.Fprintf(os.Stderr, "\n[Error]: start_page (%d) greater than total pages (%d), no output written\n\n", pageStart, page_ctr) os.Exit(6) } else if page_ctr < pageEnd { fmt.Fprintf(os.Stderr,"\n[Error]: end_page (%d) greater than total pages (%d), less output than expected\n\n", pageEnd, page_ctr); os.Exit(7) }}
这里用buf := bufio.NewReader(fin)
进行文件的读写
(5)将输出内容通过管道调用外部指令
func output_to_exc(fout io.WriteCloser, fin *os.File, pageStart int, pageEnd int, pageLen int) { line_ctr := 0 page_ctr := 1 buf := bufio.NewReader(fin) for true { bytes, err := buf.ReadByte() if err == io.EOF{ break } if line_ctr > pageLen{ page_ctr++ line_ctr = 1 } if (page_ctr >= pageStart) && (page_ctr <= pageEnd){ fout.Write([]byte{bytes}) } } if page_ctr < pageStart { fmt.Fprintf(os.Stderr, "\n[Error]: start_page (%d) greater than total pages (%d), no output written\n\n", pageStart, page_ctr) os.Exit(8) } else if page_ctr < pageEnd { fmt.Fprintf(os.Stderr,"\n[Error]: end_page (%d) greater than total pages (%d), less output than expected\n\n", pageEnd, page_ctr); os.Exit(9) }}
两个函数的区别在于输出流是不一样的
四. 测试
在github上有指令的测试,请点击这里
五. 完整代码
放在github上,请点击这里
- Go-CLI 命令行实用程序
- CLI 命令行实用程序开发实战
- 用 Go 实现简单命令行实用程序 selpg
- go实现命令行的工具cli
- Perl 作为命令行实用程序
- Perl作为命令行实用程序
- Perl 作为命令行实用程序(转)
- Go-Agenda-CLI
- Agenda-cli-service-go
- go 命令行
- mysql命令行实用程序一些实用命令
- hive cli命令行选项
- hive cli命令行选项
- hive cli命令行选项
- [VTYSH] CLI命令行----'?'
- cli - 命令行界面
- Predix 命令行 CLI 简介
- CI框架cli命令行
- Elasticsearch-常用操作符
- 手推SVM(三)-软间隔和损失函数的推导
- 1068. 万绿丛中一点红(20)
- MarkDown编辑器详细教程
- 读者写者问题(Java多线程)
- Go-CLI 命令行实用程序
- STM32的库函数
- SLAM十四讲第三章Eigen库学习笔记
- 黎曼可积的充分必要条件 (3)
- logstash配置读取文本文件使用kafka传送到kafka服务器
- 图片压缩
- 【Linux】LVM逻辑卷管理器
- VAE(Variational Autoencoder)的原理
- 我做错了