Go Online传送门

Go Online

命令行程序是什么

CLI(Command Line Interface)实用程序是Linux下应用开发的基础。正确的编写命令行程序让应用与操作系统融为一体,通过shell或script使得应用获得最大的灵活性与开发效率。Linux提供了cat、ls、copy等命令与操作系统交互;go语言提供一组实用程序完成从编码、编译、库管理、产品发布全过程支持;容器服务如docker、k8s提供了大量实用程序支撑云服务的开发、部署、监控、访问等管理任务;git、npm等都是大家比较熟悉的工具。尽管操作系统与应用系统服务可视化、图形化,但在开发领域,CLI在编程、调试、运维、管理中提供了图形化程序不可替代的灵活性与效率。


selpg的命令格式

  • -s:startPage,后面接开始读取的页号
  • -e:endPage,后面接结束读取的页号
  • -l:后面跟行数,代表多少行分为一页
  • -f:该标志无参数,代表按照分页符’\f’ 分页,一般默认72行为一页
  • -d:“-dDestination”选项将选定的页直接发送至打印机,“Destination”应该是 lp 命令“-d”选项可接受的打印目的地名称
  • input_file,output_file 2,error_file:输入文件、输出文件、错误信息文件的名字

pflag的安装和使用

  • 安装plag

    go get github.com/spf13/pflag
    
  • pflag的基本使用

    • 定义flags
    // 返回的是 指针
    var ip = flag.Int("flagname", 1234, "help message for flagname")
    
    • 将flag绑定到一个变量
    var flagvar intfunc init() {flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
    }
    
    • 绑定自定义的类型
    // 自定义类型需要实现value接口
    flag.Var(&flagVal, "name", "help message for flagname")
    
    • 解析
    // 解析函数将会在碰到第一个非flag命令行参数时停止
    flag.Parse()
    
    • 添加shorthand参数
    // func IntP(name, shorthand string, value int, usage string) *int
    // IntP is like Int, but accepts a shorthand letter that can be used after a single dash.
    var ip= flag.IntP("flagname", "f", 1234, "help message")
    
    • 设置非必须选项的默认值
    var ip = flag.IntP("flagname", "f", 1234, "help message")
    flag.Lookup("flagname").NoOptDefVal = "4321"
    

其他包的使用

  • os:参考博客Go 标准库介绍三: os或查看官方文档
  • io: 参考博客Go 标准库介绍五: io或查看官方文档
  • bufio: 参考博客[Golang学习 - bufio 包](https://www.cnblogs.com/golove/p/3282667.html)或查看官方文档
  • os/exec(简单用法): 参考博客golang exec 命令执行

代码设计

阅读源码selpg.c, 根据类似的结构写

  • 创建结构体表示命令行参数

    type selpg_args struct{spage int        //start pageepage int       //end pageplen int      //page lengthptype int      //page type, -f , default: -linfile string  //input fileprintdest string //print destination
    }
    
  • main函数

    func main() {selArgs := selpg_args{}programName = os.Args[0]initArgs(&selArgs)              //init the flag arguments, setting default values, and get arguments from the command linehandleArgs(len(os.Args), &selArgs) //handle the argumentsprocess(&selArgs)                //run the CLI command
    }
    
  • 接下俩分为initArgs(&selpgArgs)、 handleArgs(len(os.Args), &selpgArgs)、process(&selArgs)三部分

    • initArgs(&selpgArgs) 初始化flag变量,并解析

      func initArgs(args *selpg_args) {pflag.Usage = func() {fmt.Fprintf(os.Stderr,"USAGE: \n%s -s start_page -e end_page [ -f | -l lines_per_page ]" + " [ -d dest ] [ in_filename ]\n", )pflag.PrintDefaults()}pflag.IntVarP(&args.spage,"start", "s", 0, "start page")pflag.IntVarP(&args.epage,"end","e", 0, "emd page")//这里跟官方文档不同,使用10行作为默认值pflag.IntVarP(&args.plen,"linenum", "l", 10, "page length (lines)")pflag.BoolVarP(&args.ptype,"printdes","f", false, "'l' for lines-delimited, 'f' for form-feed-delimited. default is 'l'")pflag.StringVarP(&args.printdest, "othertype","d", "", "print destination")pflag.Parse() //解析
      }
      
    • handleArgs(argNum int, args *selpg_args) 处理参数

      func handleArgs(argNum int, args *selpg_args) {/* 检查参数合不合法 */if argNum < 3 {fmt.Fprintf(os.Stderr, "%s: not enough arguments\n", programName)pflag.Usage()os.Exit(1)}/* 第一个参数,spage*/if os.Args[1][0] != '-' || os.Args[1][1] != 's' {fmt.Fprintf(os.Stderr, "%s: 1st arg should be -s=spage\n", programName)pflag.Usage()os.Exit(2)}if args.spage < 1  {fmt.Fprintf(os.Stderr, "%s: invalid start page %s\n", programName, args.spage)pflag.Usage()os.Exit(3)}/* 第二个参数,epage*/if os.Args[3][0] != '-' || os.Args[3][1] != 'e' {fmt.Fprintf(os.Stderr, "%s: 2nd arg should be -e=epage\n", programName)pflag.Usage()os.Exit(4)}if args.epage < 1  || args.epage < args.spage  {fmt.Fprintf(os.Stderr, "%s: invalid end page %s\n", programName, args.epage)pflag.Usage()os.Exit(5)}/* 处理可选择的参数 */if args.plen != 5 {if args.plen < 1  {fmt.Fprintf(os.Stderr, "%s: invalid page length %s\n", programName, args.plen)pflag.Usage()os.Exit(6)}}/* 第三个参数,infile*/if pflag.NArg() > 0 {args.infile = pflag.Arg(0)/*检查文件是否存在 */file, err := os.Open(args.infile)if err != nil {fmt.Fprintf(os.Stderr, "%s: input file \"%s\" does not exist\n", programName, args.infile)os.Exit(7)}/* 是否可读 */file, err = os.OpenFile(args.infile, os.O_RDONLY, 0666)if err != nil {if os.IsPermission(err) {fmt.Fprintf(os.Stderr, "%s: input file \"%s\" exists but cannot be read\n", programName, args.infile)os.Exit(8)}}file.Close()}
      }
    • handle(args *selpg_args) 处理错误信息

      func handle(args *selpg_args) {//错误处理if len(os.Args) < 3 {   /* Not enough args, minimum command is "selpg -sstartpage -eend_page"  */fmt.Fprintf(os.Stderr, "\n%s: not enough arguments\n", programName)pflag.Usage()os.Exit(1)}/* handel -s */if os.Args[1] != "-s" {fmt.Fprintf(os.Stderr, "\n%s: 1st arg should be -s start_page\n", programName)pflag.Usage()os.Exit(2)}i := 1 << 32 - 1if(args.spage < 1 || args.spage > i) {fmt.Fprintf(os.Stderr, "\n%s: invalid start page %s\n", programName, os.Args[2])pflag.Usage()os.Exit(3)}/* handle -e*/if os.Args[3] != "-e" {fmt.Fprintf(os.Stderr, "\n%s: 2nd arg should be -e end_page\n", programName)pflag.Usage()os.Exit(4)}if args.epage < 1 || args.epage > i || args.epage < args.epage {fmt.Fprintf(os.Stderr, "\n%s: invalid end page %i\n", programName, args.epage)pflag.Usage()os.Exit(5)}//if len(pflag.Args()) == 1 {_, err := os.Stat(pflag.Args()[0])/* check if file exists */if err != nil && os.IsNotExist(err) {fmt.Fprintf(os.Stderr, "\n%s: input file \"%s\" does not exist\n",programName, pflag.Args()[0]);os.Exit(6);}args.infile = pflag.Args()[0]}
      }
      
    • process(args *selpg_args) 运行结果

      func process(args *selpg_args) {fin := os.Stdinfout := os.Stdoutvar (pageCnt intlineCnt interr errorerr1 errorerr2 errorline stringcmd *exec.Cmdstdin io.WriteCloser)/* 处理fin输入 */if args.infile != "" {fin, err1 = os.Open(args.infile)if err1 != nil {fmt.Fprintf(os.Stderr, "%s: could not open input file \"%s\"\n", programName, args.infile)os.Exit(11)}}if args.printdest != "" {//使用exec库的Command的函数调用,command返回cmd结构来执行带有相关参数的命令,它仅仅设定cmd结构中的Path和Args参数cmd = exec.Command("cat", "-n")//指定一个对象连接到对应的管道进行传输参数(stdinpipe),获取输出(stdoutpipe),获取错误(stderrpipe)stdin, err = cmd.StdinPipe()if err != nil {fmt.Println(err)}} else {stdin = nil}/* 读取文件 */
      //NewReader 相当于 NewReaderSize(rd, 4096)rd := bufio.NewReader(fin)if args.ptype == false {lineCnt = 0        //记录读了多少行pageCnt = 1       //记录读了多少页for true {//用readString的方式读取,直到遇到换行符\n才停止读取line, err2 = rd.ReadString('\n')if err2 != nil { /* error or EOF */break}lineCnt++if lineCnt > args.plen {  //到达设定的每页最大行数pageCnt++lineCnt = 1}if pageCnt >= args.spage && pageCnt <= args.epage {fmt.Fprintf(fout, "%s", line)}}} else {pageCnt = 1for true {//用ReadByte的方式读取c, err3 := rd.ReadByte()if err3 != nil { /* error or EOF */break}if c == '\f' { //以换页符为标志记录读过的页数pageCnt++}if pageCnt >= args.spage && pageCnt <= args.epage {fmt.Fprintf(fout, "%c", c)}}fmt.Print("\n")}/* 循环后的处理*/// pageCnt比开始页面还小,说明出错了if pageCnt < args.spage {fmt.Fprintf(os.Stderr, "%s: spage (%d) greater than total pages (%d), no output written\n", programName, args.spage, pageCnt)} else if pageCnt < args.epage { //pageCnt比结束页面小,同样也是出错fmt.Fprintf(os.Stderr, "%s: epage (%d) greater than total pages (%d), less output than expected\n", programName, args.epage, pageCnt)}//输出文件名不为空if args.printdest != "" {stdin.Close()cmd.Stdout = foutcmd.Run()}fmt.Fprintf(os.Stderr,"\n---------------\nProcess end\n")fin.Close()fout.Close()
      }
      

测试结果

测试文件名称为 in

内容为999行数字(1-999), 每一行的数字表示行数

  • selpg -s1 -e1 input_file

  • selpg -s1 -e1 < input_file

  • other_command | selpg -s10 -e20


这个是因为文件没有那么多页造成的,实际上文件只有一页(没有换页符\f), 换一下参数就行了

  • selpg -s1 -e2 input_file >output_file

    控制台并没有输出,输出的信息保存在out文件中

  • selpg -s1 -e2 input_file 2>error_file

这是因为我的参数后面直接加数字没有隔开空格造成的erro,可能是因为我是ubuntu16.04的原因。


这样就正常了

  • selpg -s10 -e20 input_file >output_file 2>error_file

将正确的信息输出到out文件,错误信息输出到error文件

  • selpg -s10 -e20 input_file >output_file 2>/dev/null


标准输出被重定向至“out”;selpg 写至标准错误的所有内容都被重定向至 /dev/null(空设备),这意味着错误消息被丢弃了。

  • selpg -s10 -e20 input_file >/dev/null

    标准输出被丢弃;错误消息在屏幕出现。

  • 其他的测试就不多做介绍了


总结

本次实验要求去阅读源码并将其用Go语言形式表达出来,开发一个简单的selpg命令行指令。其中有一些东西没有提示需要自己去查,比如怎么直接在终端使用selpg指令而不是在IDE里运行,需要设置环境变量。先将selpg.go程序go build,再go install到全局变量文件夹(也可以手动复制到全局变量的文件夹里),之后就可以像我上面那样测试了。

本次实验使我学习了更多的包和函数知识,锻炼了go语言的编程能力。

服务计算:简单的CLI程序相关推荐

  1. 服务计算作业三——CLI 命令行实用程序开发基础

    服务计算作业三--CLI 命令行实用程序开发基础 18342138 郑卓民 本次作业gitee仓库链接(完整代码) 概述 CLI(Command Line Interface)实用程序是Linux下应 ...

  2. [服务计算] 简单 web 服务与客户端开发实战

    GitHub: 项目文档: https://github.com/fentender/book-blog-doc 客户端: https://github.com/fentender/book-blog ...

  3. 服务计算--简单 web 服务与客户端开发实战

    一.概述 利用 web 客户端调用远端服务是服务开发本实验的重要内容.其中,要点建立 API First 的开发理念,实现前后端分离,使得团队协作变得更有效率. 任务目标 选择合适的 API 风格,实 ...

  4. 计算两个经纬度之间的距离软件_小程序使用腾讯位置服务计算两地之间的距离(有源码)...

    背景: 在最近的小程序开发中,需要计算当前位置到目标位置之间的距离.背靠"腾讯爸爸",没有理由不使用腾讯的位置服务啊!趁着周末把使用方式整理一下,还写了一个demo,和大家分享一下 ...

  5. c语言12之编程设计一个简单的计算器程序,要求根据用户从键盘输入的表达式:操作数1 运算符op 操作数2 计算表达式的值,指定的运算符为加减乘除。

    题目: 设计一个简单的计算器程序,要求根据用户从键盘输入的表达式: 操作数1 运算符op 操作数2 计算表达式的值,指定的运算符为加减乘除. 源代码: #include<stdio.h> ...

  6. 为什么通过微服务的方法构建应用程序?

    作为软件开发人员,我们已知道思考如何将应用程序因数分解成组件部分. 这是对象导向.软件抽象和组件化的中心模式. 现在,这种因数分解往往以共享库和技术层之间的类与接口呈现. 通常采用一种分层方法,有后端 ...

  7. 安卓之位置服务(简单定位用户所在的位置)

    *位置的服务(Location Based Service) 基于位置的服务简称LBS,主要的工作原理就是利用无线电通讯网络或GPS等定位方式来确定出移动设备所在的位置. 基于位置的服务所围绕的核心就 ...

  8. 无服务计算的未来和挑战: A Berkeley View on Serverless Computing

    加州大学伯克利分校继 2009 年发布 <The Berkeley View on Cloud Computing>一举拨开云计算迷雾,十年后又一次发布了 <A Berkeley V ...

  9. 无服务计算应用场景探讨及 FaaS 应用实战

    简介: 无服务计算本身是一个概念或者理论模型,落地到具体技术上主要有函数即服务(FaaS)以及后端即服务(BaaS)两种形式,阿里云提供函数即服务 FaaS 产品. 作者 | 宋文龙(闻可)  阿里云 ...

最新文章

  1. java产生全局唯一的int类型_全局唯一ID设计
  2. mysql 10分钟_10分钟入门mysql(含常用的sql语句,mysql常见问题及解决方案)
  3. 时永方:做到这三点,你就是多媒体内行了
  4. 一般筛法和快速线性筛法求素数 求素数的一点总结
  5. 《红楼梦》告诉你,什么才是真正的教养
  6. Google 地图 google map api / 地图有关
  7. 关于“就地颠倒句子里的词”面试题
  8. xhtml标签和html标签,XHTML常用标签
  9. ASP.NET WEB API简介
  10. 考研408 完整知识点篇2.0版
  11. 1分钟教会你cad如何转pdf
  12. 加载行为:未加载用户已选择禁用宏
  13. 带圆圈的数字和markdown常用表达式记录
  14. Android HPSocket SE_SOCKET_CREATE (3)
  15. chrome谷歌浏览器使用技巧
  16. php使用逻辑运算符提交程序运行效率
  17. C++后台开发推荐的书
  18. 非主流闪图头像教程:扩散粒子效果
  19. centos7的LAMP多机LAP+MYSQL
  20. 集成显卡和独立显卡哪个好 集成显卡与独立显卡区别

热门文章

  1. 开启xterm终端256色和终端下vim 256色
  2. python3默认使用的编码是_Python3.x默认使用的编码是encoding
  3. ComponentOne Studio WinUI
  4. 亿图脑图协同版全面升级,团队协作更高效!
  5. Java15异常处理
  6. ROS中自定义带有header的消息文件
  7. genymotion能设置中文_genymotion 中文输入法
  8. 【Python】DataFrame使用drop_duplicates()函数去重(不)保留重复值,取重复值
  9. java数组赋值_java中给数组赋值的方法
  10. uC/OS-II 移植 内核系统裁剪os_cfg.h