服务计算学习之路二——selpg命令行程序编写
作业内容
- 一句话:用Golang重写别人用C语言写好的开发Linux命令行使实用程序
实验前
说真的,不知是原作者的博文跨度大还是我的cli程序编写基础差(也确实没怎么接触过),看完原作者的介绍后,还是一头雾水,还好给了C语言源码,虽然有个别语句看不懂,但总体还是看懂了。简单来说,做的是一个帮助用户打印文件中特定的页的一个命令行小工具。
实验思路
参考原作者的思路:
- 检查用户输入的命令行参数是否合法,如果不合法则输出错误,并给出程序的用法,终止程序
- 如果合法则解析参数,赋值到相应的变量里,这个步骤里Go语言提供了flag包能更好地处理命令行参数问题,如果涉及到文件的问题,还要检查文件的路径是否正确,是否有权限读写之类的。
- 最后执行程序。
- 由实验思路来看似乎很简单,但对于刚入门Go不久并且又对cli程序不太了解的我还是有点吃力的,最后实现的demo也没有原作者的功能完全。
实验步骤
-
Step1:用pflag代替Go自带的flag包
根据老师的要求,用pflag代替Go自带的flag包,其实这两个包的大部分功能都一样,用法也基本一样,不过这两个包对应的Unix命令行程序风格不一样,下载pflag包:
go get github.com/spf13/pflag
使用时建议用import( flag "github.com/spf13/pflag" )
这样就能使用flag相关的代码了,可以提高程序的可移植性。不过我一开始没知道能这样用,所以下面的程序中我还是用了pflag
-
Step2:创建selpg程序对应的变量
const MAX_INT = int(^uint(0) >> 1) type selpgArgs struct { // selpg -s startPage -e endPage [-l linePerPage | -f ][-d dest] filename startPage int //开始页码 endPage int //结束页码 filename string //文件名 pageLength int // 每页长度 pageType bool // 页类型 printDest string // 输出地址 }
用了一个结构体来定义。
-
Step3:用pflag包进行参数的绑定,关于flag包的一些教程:Golang之使用Flag和Pflag,命令行参数解析flag
/*InitArgs sss */ func InitArgs(p *selpgArgs) { pflag.Usage = Usage pflag.IntVarP(&p.startPage, "start", "s", 1, "Start Page Number") pflag.IntVarP(&p.endPage, "end", "e", 1, "End Page Number") pflag.IntVarP(&p.pageLength, "LinePerPage", "l", 72, "Line in Each Page") pflag.BoolVarP(&p.pageType, "printType", "f", false, "flag Splits Page") pflag.StringVarP(&p.printDest, "printDest", "d", "", "Destinaton of Print") }
这里我定义了一个函数实现对参数的绑定,模块化,这样,但输入的参数有
-s
或-e
等,这段代码会自动将后面带的参数绑定到对应的变量里另外,最好也绑定pflag.Usage:func Usage() { fmt.Fprintf(os.Stderr, "Usage:\tselpg -s startPage -e endPage [-l linePerPage | -f ][-d dest] filename\n") fmt.Fprintf(os.Stderr, "\t-s=Number\tStart Page Number\n") fmt.Fprintf(os.Stderr, "\t-e=Number\tLine in Each Page\n") fmt.Fprintf(os.Stderr, "\t-l=Number\tStart Page Number\n") fmt.Fprintf(os.Stderr, "\t-f=BoolValue(true or false)\tflag Splits Page\n") fmt.Fprintf(os.Stderr, "\t-d=string\tDestinaton of Print\n") fmt.Fprintf(os.Stderr, "\t[filename]\t\n") }
这个函数是在命令行参数为
-h
或--help
时输出的帮助菜单。 -
Step4:接下来就是检查参数的正确性与否,这一部分一般没什么问题:
func ProcessArgs(ac int, selpg *selpgArgs) { if ac < 3 { fmt.Fprintf(os.Stderr, "Not enough arguments\n") pflag.Usage() os.Exit(1) } else if os.Args[1][0] != '-' && os.Args[1][1] != 's' { fmt.Fprintf(os.Stderr, "First arg should be -s=Number\n") pflag.Usage() os.Exit(2) } else if selpg.startPage < 1 || selpg.startPage > (MAX_INT-1) { fmt.Fprint(os.Stderr, "StartPage Number invalid\n") pflag.Usage() os.Exit(3) } else if os.Args[3][0] != '-' && os.Args[3][1] != 'e' { fmt.Fprintf(os.Stderr, "Second arg should be -e=Number\n") pflag.Usage() os.Exit(4) } else if selpg.endPage < 1 || selpg.endPage > (MAX_INT-1) || selpg.startPage < selpg.endPage { fmt.Fprintf(os.Stderr, "EndPage Number invalid\n") pflag.Usage() os.Exit(5) } else if selpg.pageLength < 1 { fmt.Fprintf(os.Stderr, "PageLength invalid\n") pflag.Usage() os.Exit(6) } if pflag.NArg() > 0 { selpg.filename = pflag.Arg(0) // check if the file exits file, err := os.Open(selpg.filename) if err != nil { fmt.Fprintf(os.Stderr, "The filename doesn't exits\n") os.Exit(7) } //reader = bufio.NewReader(file) file.Close() } }
-
Step5: 检查完参数后,都合法的话就执行对应的操作,注意一下文件的读和写就差不多了:
func ProcessInput(selpg *selpgArgs) { fout := os.Stdout result := "" pageCount := 0 lineCount := 0 reader := bufio.NewReader(os.Stdin) if selpg.filename != "" { file, err := os.Open(selpg.filename) if err != nil { fmt.Fprintf(os.Stderr, "Can not open the file\n") os.Exit(8) } defer file.Close() reader = bufio.NewReader(file) } if selpg.printDest != "" { file, err := os.OpenFile(selpg.printDest,os.O_RDWR,0777) if err != nil { fmt.Fprintf(os.Stderr, "Can not open the destination file!\n") os.Exit(9) } defer file.Close() fout = file } if selpg.pageType == true { pageCount = 1 for { str, err := reader.ReadString('\f') if err == io.EOF || pageCount > selpg.endPage { break } else if err != nil { fmt.Fprintf(os.Stderr, "Read File Error1!\n") os.Exit(10) } pageCount++ fout.Write([]byte(str)) result = strings.Join([]string{result, str}, "") } } else { pageCount = 1 lineCount = 0 for { str, err := reader.ReadString('\n') if err == io.EOF || pageCount > selpg.endPage { break } else if err != nil { fmt.Fprintf(os.Stderr, "Read File Error2!\n") } lineCount++ if lineCount > selpg.pageLength { lineCount = 1 pageCount++ } fout.Write([]byte(str)) result = strings.Join([]string{result, str}, "") } } if selpg.printDest != "" { cmd := exec.Command("lp", "-d"+selpg.printDest) cmd.Stdin = strings.NewReader(result) var stderrOut bytes.Buffer cmd.Stderr = &stderrOut err := cmd.Run() if err != nil { fmt.Fprintf(os.Stderr, stderrOut.String()) } } }
执行结果:
- 执行语句:
./selpg.exe -h
./selpg.exe -s 1 -e 1 -d output.txt input.txt
input.txt 内容:
- 其它命令就不一一展示了
总结
虽然做出来的demo没有原作者的功能完美,但也从中学习了很多东西,也增强了对Go的学习和理解。