文章目录

  • 一. 概述
  • 二. Golang 的支持
    • ① 使用 os 包处理参数
    • ② 使用 flag 包处理参数
  • 三. 开发实践
    • ① 项目要求
    • ② 设计说明
      • 1) selpg 定义
      • 2) 代码设计
        • 1. 使用 pflag 替代 goflag 以满足 Unix 命令行规范
        • 2. 参数结构体 selpg_args
        • 3. 处理参数 process_args
        • 4. 运行逻辑 process_input
        • 5. usage 命令格式(提示用户)
        • 6. main 主函数
    • ③ 功能测试结果
      • 1) 测试前准备
      • 2) 参考 `使用 selpg` 来输入测试命令
        • 1. selpg -s1 -e1 input.txt
        • 2. selpg -s1 -e1 < input.txt
        • 3. more input.txt | selpg -s1 -e2
        • 4. selpg -s1 -e2 input.txt >output.txt
        • 5. selpg -s10 -e20 input.txt 2>error.txt
        • 6. selpg -s1 -e20 input.txt >output.txt 2>error.txt
        • 7. selpg -s1 -e2 input.txt >output.txt 2>/dev/null
        • 8. selpg -s10 -e20 input.txt >/dev/null
        • 9. selpg -s1 -e2 input.txt | wc
        • 10. selpg -s1 -e2 -l66 input.txt
        • 11. selpg -s1 -e2 -f input.txt
    • ④ 单元测试
      • 1)测试 process_agrs
      • 2)测试 process_input
      • 3)测试 usage
      • 4)测试 main
      • 5)测试全部

项目完整代码所在 gitee 仓库
https://gitee.com/beilineili/hello

一. 概述

二. Golang 的支持

① 使用 os 包处理参数

package mainimport ("fmt""os"
)func main() {for i, a := range os.Args[1:] {fmt.Printf("Argument %d is %s\n", i+1, a)}}
  • 在终端输入参数并测试,os.Args 会接受输入的参数

② 使用 flag 包处理参数

package mainimport ("flag" "fmt"
)func main() {var port intflag.IntVar(&port, "p", 8000, "specify port to use.  defaults to 8000.")flag.Parse()fmt.Printf("port = %d\n", port)fmt.Printf("other args: %+v\n", flag.Args())
}
  • 在终端输入参数并测试,指定了端口8000,并使用 flag.Args 来接受输入参数

三. 开发实践

① 项目要求

  • 使用 golang 开发 开发 Linux 命令行实用程序 中的 selpg
  • 提示:
  • ① 请按文档 使用 selpg 章节 要求测试你的程序
  • ② 请使用 pflag 替代 goflag 以满足 Unix 命令行规范, 参考:Golang之使用Flag和Pflag
  • ③ golang 文件读写、读环境变量,请自己查 os 包
  • ④ “-dXXX” 实现,请自己查 os/exec 库,例如案例 Command,管理子进程的标准输入和输出通常使用 io.Pipe,具体案例见 Pipe
  • ⑤ 请自带测试程序,确保函数等功能正确

② 设计说明

使用 selpg 章节

1) selpg 定义

  • selpg 是从文本输入选择页范围的实用程序。该输入可以来自作为最后一个命令行参数指定的文件,在没有给出文件名参数时也可以来自标准输入。
  • 主要作用是:读取用户参数、处理用户参数、读取输入、将选中页数据送入输出地址。
  • 使用该工具可以选择只打印需要打印的页面
  • 在 开发 Linux 命令行实用程序 中有 slepg.c 的源代码,因此我们要完成的任务是将它转换成 go 语言版本,从中学习 go 的基本语法
  • selpg 的使用命令为
selpg [-s startPage] [-e endPage] [-l linesPerPage | -f] [-d print Dest] [filename]
  • 对应参数为 s-开始页,e-结束页,l-页长,换页符-f,d-输出地址,filename-输入文件名

2) 代码设计

1. 使用 pflag 替代 goflag 以满足 Unix 命令行规范
  • 参考 Golang之使用Flag和Pflag
  • 在终端输入命令安装到本地
go get -u github.com/spf13/pflag
  • 在 import 里添加 “github.com/spf13/pflag”,用于解释命令行参数
  • bufio:用于文件的读写
  • io:用于文件读写,读文件变量
import ("github.com/spf13/pflag""io""bufio""os/exec""fmt""os"
)
  • 全局变量
var progname string   //程序名const INT_MAX = int(^uint(0) >> 1);  //检查一个数是否为有效整数
2. 参数结构体 selpg_args
  • 用于记录数据的结构体
type selpg_args struct {start_page intend_page intin_filename stringpage_len intpage_type boolprint_dest string
}
3. 处理参数 process_args
  • 为了更方便使用 pflag 包而绑定参数,使得 arg 能够获取输入参数的值
    pflag.IntVarP(&sa.start_page, "start_page", "s", 0, "Start page")pflag.IntVarP(&sa.end_page, "end_page", "e", 0, "End page")pflag.BoolVarP(&sa.page_type, "page_type", "f", false, "Page type")pflag.IntVarP(&sa.page_len, "page_len", "l", 72, "Lines per page")pflag.StringVarP(&sa.print_dest, "dest", "d", "", "Destination")pflag.Usage = func() {usage()pflag.PrintDefaults()}pflag.Parse()sa.in_filename = ""if remain := pflag.Args(); len(remain) > 0 {sa.in_filename = remain[0]}
  • 判断参数是否合法
  • 开始和结束页不能小于等于0,且开始页不能大于结束
  • 页长度不能小于等于0
  • 若不符合规范,则打印错误类型并终止;若符合则打印到屏幕上
    //参数不够if len(os.Args) < 3 { fmt.Fprintf(os.Stderr, "%s:  not enough arguments\n", progname)pflag.Usage()os.Exit(1)}//起始页非法if sa.start_page < 1 || sa.start_page > (INT_MAX-1) { fmt.Fprintf(os.Stderr, "%s:  invalid start page %d\n", progname, sa.start_page)pflag.Usage()os.Exit(2)}//结束页非法if sa.end_page < 1 || sa.end_page > (INT_MAX-1) || sa.end_page < sa.start_page {fmt.Fprintf(os.Stderr, "%s:  invalid end page %d\n", progname, sa.start_page)pflag.Usage()os.Exit(3)}//页长度非法if sa.page_len < 1 || sa.page_len > (INT_MAX-1) {fmt.Fprintf(os.Stderr, "%s:  invalid page length %d\n", progname, sa.page_len)pflag.Usage()os.Exit(4)}if sa.in_filename != "" {if _, err := os.Stat(sa.in_filename); os.IsNotExist(err) {fmt.Fprintf(os.Stderr, "%s:  input file \"%s\" does not exist\n", progname, sa.in_filename)pflag.Usage()os.Exit(5)}}
4. 运行逻辑 process_input
  • 读取输入 和 写入输出
     var reader *bufio.Reader //读取输入var writer io.WriteCloser //写入输出
  • 使用 os 包 进行 golang 文件读写、读环境变量
  • 如果用户输入了in_filename,就将文件作为输入,否则将命令行作为输入
     //readerif sa.in_filename == "" {reader = bufio.NewReader(os.Stdin)} else {fin, err := os.Open(sa.in_filename)if err != nil {fmt.Fprintf(os.Stderr, "%s: could not open input file \"%s\"\n", progname, sa.in_filename)os.Exit(6)}reader = bufio.NewReader(fin)defer fin.Close()}
  • “-dXXX" 实现:若参数中包含 -dXXX 则使用 lp -dXXX 指令将输入送入指定打印机,否则输出到命令行。需要借助 os/exec 库
     //writerif sa.print_dest == "" {writer = os.Stdout} else {cmd := exec.Command("lp","-d"+ sa.print_dest)var err errorif writer, err = cmd.StdinPipe(); err != nil {fmt.Fprintf(os.Stderr, "%s: could not open pipe to \"%s\"\n",progname, sa.print_dest)fmt.Println(err)os.Exit(7)}cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err = cmd.Start(); err != nil {fmt.Fprintf(os.Stderr, "%s: cmd start error\n",progname)fmt.Println(err)os.Exit(8)}}
  • 将指定页范围的数据送入输出地址
     line_ctr, page_ctr, pLen := 1, 1, sa.page_lenptFlag := '\n'if sa.page_type {ptFlag = '\f'pLen = 1}//用reader读取选中页的数据,写入writerfor {line, crc := reader.ReadString(byte(ptFlag));if crc != nil && len(line) == 0 {break}if line_ctr > pLen {page_ctr++line_ctr = 1}if page_ctr >= sa.start_page && page_ctr <= sa.end_page {_, err := writer.Write([]byte(line))if err != nil {fmt.Println(err)os.Exit(9)}}line_ctr++}
  • 判断读取是否成功或者是否完成
  if page_ctr < sa.start_page {fmt.Fprintf(os.Stderr,"\n%s: start_page (%d) greater than total pages (%d),"+" no output written\n", progname, sa.start_page, page_ctr)} else if page_ctr < sa.end_page {fmt.Fprintf(os.Stderr, "\n%s: end_page (%d) greater than total pages (%d),"+" less output than expected\n", progname, sa.end_page, page_ctr)}
5. usage 命令格式(提示用户)
  • usage 函数会输出 selpg 命令的格式,作为用户输入有误时的提示使用。
func usage() {fmt.Fprintf(os.Stderr, "\nUSAGE: %s -sstart_page -eend_page [ -f | -llines_per_page ] [ -ddest ] [ in_filename ]\n", progname)
}
6. main 主函数
  • 先对 selpg_args 结构体和 progname 进行初始化
  • 调用 process_agrs 函数处理输入时的参数错误
  • 调用 process_input 函数来执行输入的参数
func main() {sa := selpg_args{}progname = os.Args[0]sa.process_args()sa.process_input()
}

③ 功能测试结果

  • 确保函数等功能正确

1) 测试前准备

  • 注意需要在 GOPATH 文件路径处新建 selpg 文件夹,将 selpg.go 文件放入其中。然后在终端执行命令 go install selpg 才可以在全局使用 selpg 命令
  • 将代码文件 selpg.go 直接作为 input.txt
  • output.txt 用于保存输出的文本,初始为空
  • error.txt 用于保存错误信息,初始为空

2) 参考 使用 selpg 来输入测试命令

使用 selpg 网址

1. selpg -s1 -e1 input.txt
  • 该指令会将第一页的内容写至标准输出(也就是屏幕)上,可以看到最后一行对应的是代码的第72行

2. selpg -s1 -e1 < input.txt
  • 该命令与示例 1 所做的工作相同,但在本例中,selpg 读取标准输入,而标准输入已被 shell/内核重定向为来自“input_file”而不是显式命名的文件名参数。输入的第 1 页被写至屏幕

3. more input.txt | selpg -s1 -e2
  • “other_command”的标准输出被 shell/内核重定向至 selpg 的标准输入。将第 1 页到第 2 页写至 selpg 的标准输出(屏幕)
  • 可见输出到了第144行


4. selpg -s1 -e2 input.txt >output.txt
  • selpg 将第 1 页到第 2 页写至标准输出;标准输出被 shell/内核重定向至“output.txt"
5. selpg -s10 -e20 input.txt 2>error.txt
  • selpg 将第 10 页到第 20 页写至标准输出(屏幕);所有的错误消息被 shell/内核重定向至“error.txt”。因为我们的 input.txt 只有3页,所以会输出错误信息到 error.txt。请注意:在“2”和“>”之间不能有空格;

  • 如果使用的是 selpg -s1 -e2 input.txt 2>error.txt,则会正常打印第1-2页(到144行),error.txt 里不含任何错误信息
6. selpg -s1 -e20 input.txt >output.txt 2>error.txt
  • selpg 将第 1 页到第 20 页写至标准输出,标准输出被重定向至“output.txt”;selpg 写至标准错误的所有内容都被重定向至“error.txt”。
  • 因为没有 20 页那么多,在 error.txt 会输出错误信息

7. selpg -s1 -e2 input.txt >output.txt 2>/dev/null
  • selpg 将第 1 页到第 2 页写至标准输出(屏幕),标准输出被重定向至“output.txt”;selpg 写至标准错误的所有内容都被重定向至 /dev/null(空设备),意味着错误消息被丢弃了。
8. selpg -s10 -e20 input.txt >/dev/null
  • selpg 将第 10 页到第 20 页写至标准输出,标准输出被丢弃;错误消息在屏幕出现。
9. selpg -s1 -e2 input.txt | wc
  • selpg 的标准输出透明地被 shell/内核重定向,成为“other_command”的标准输入,第 1 页到第 2 页被写至该标准输入。这里的 other_command 是 wc,它会显示选定范围的页中包含的行数、字数和字符数。

10. selpg -s1 -e2 -l66 input.txt
  • 通过 -l 指定每一页的行数,最后一行是代码的第132行


11. selpg -s1 -e2 -f input.txt
  • 假定页由换页符定界。第 1 页到第 2 页被写至 selpg 的标准输出(屏幕)。通过 -f 表示每一页以 ’\f’ 为标志

  • 最后一行是input.txt中第二个换页符的位置


所有指令都测试完毕,程序功能完成

④ 单元测试

  • 如果说功能测试是站在用户的角度,从外部测试应用查看是否达到效果;那么单元测试则是站在程序员的角度,从内部测试应用
  • 我们写好测试代码,对每个函数分别进行单元测试,代码如下:
package mainimport "testing"func Test_process_args(t *testing.T) {tests := []struct {name stringsa   *selpg_args}{}for _, tt := range tests {t.Run(tt.name, func(t *testing.T) {tt.sa.process_args()})}
}func Test_process_input(t *testing.T) {tests := []struct {name stringsa   selpg_args}{}for _, tt := range tests {t.Run(tt.name, func(t *testing.T) {tt.sa.process_input()})}
}func Test_usage(t *testing.T) {tests := []struct {name string}{{name: "Test_usage1"},{name: "Test_usage2"},}for _, tt := range tests {t.Run(tt.name, func(t *testing.T) {usage()})}
}func Test_main(t *testing.T) {tests := []struct {name string}{}for _, tt := range tests {t.Run(tt.name, func(t *testing.T) {main()})}
}
  • 测试如下:

1)测试 process_agrs

2)测试 process_input

3)测试 usage

4)测试 main

5)测试全部

服务计算 HW3 CLI 命令行实用程序开发基础相关推荐

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

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

  2. 基于Golang的CLI 命令行程序开发

    基于Golang的CLI 命令行程序开发 [阅读时间:约15分钟] 一. CLI 命令行程序概述 二. 系统环境&项目介绍&开发准备 1.系统环境 2.项目介绍 3.开发准备 三.具体 ...

  3. Arduino CLI命令行ESP32开发环境搭建(Linux Ubuntu操作系统)

    陈拓2023/03/06-2023/03/11 简介 Arduino cli是一个命令行界面,您可以使用它创建草图(sketch)并将其上传到开发板中.它提供了ArduinoIDE的所有功能: 编写s ...

  4. 服务计算 - 3 Golang开发Linux命令行实用程序 - selpg

    文章目录 Golang开发Linux命令行实用程序 - selpg 1. 介绍 2. 设计与实现 2.1 设计思路 2.2 功能模块划分与实现 3. 参考文献 Golang开发Linux命令行实用程序 ...

  5. 命令行工具开发:如何快速实现命令行提示?

    简介:对于稍微复杂一些的命令行工具,命令行的提示功能必不可少.那么对于不同语言的开发者,有没有一种简单快捷的实现方式呢?本文分享一种快速实现的方法,使用YAML文件定义命令行工具的使用规范,再通过工具 ...

  6. 使用 CUPS 命令行实用程序设置和管理打印机

    使用 CUPS 命令行实用程序设置和管理打印机 本节提供 CUPS 命令的简介并介绍如何设置和管理打印机. CUPS 命令行实用程序 CUPS 提供用于设置打印机以及使网络中的系统可以访问这些打印机的 ...

  7. 完全命令行.NET开发

    很久没写日记了,今天走一个 Windows系统的GUI太好用了,以至于很多Windows开发者都不知道还有更好用的命令行模式.命令行模式可以把自己经常使用的功能写成脚本,一个命令就可以全部工作执行掉, ...

  8. add-apt-repository:找不到命令_手把手教你使用nodejs编写cli(命令行)

    手把手教你使用nodejs编写cli(命令行) 前端日常开发中,会遇见各种各样的cli,比如一行命令帮你打包的webpack,一行命令帮你生成vue项目模板的vue-cli,还有创建react项目的c ...

  9. 华为(huawei)USG6000的CLI命令行综合配置之Ensp真机连接 USG6000防火墙

    文章目录 前言 一.拓扑图及拓扑说明 二.配置步骤及验证 1.配置真机连接USG6000 前言 华为的USG系列防火墙一般部署在园区网出口中,很多朋友对USG系列如何进行配置不是太了解.本案例将用en ...

最新文章

  1. CB:南土所梁玉婷组-细菌群落的高稳定性和代谢能力促进了土壤中易分解碳的快速减少...
  2. as3 内容自适应容器大小
  3. 经验 | 深度学习中从基础综述、论文笔记到工程经验、训练技巧
  4. Linux 获取屏幕分辨率与窗口行列数(c/c++)
  5. H5炫酷特效系列1——canvas满屏幕变换爱心示例
  6. 中国数字绘图板行业市场供需与战略研究报告
  7. Q85:对比“直接光照”和“间接光照”的反射模型
  8. 利用HTML制作简易新闻网站的静态网页
  9. 菜鸟的MySQL学习笔记(二)
  10. 使用 Java 操作 Kubernetes API
  11. php unexpected t_if,zblog提示syntax error, unexpected 'if' (T_IF)出错,模版压缩导致PHP提示代码错误...
  12. Mac10.14版本安装虚拟机Parallels Desktop 14和windows7 64位旗舰版镜像
  13. 人才测评之计划能力,如何提高计划能力?
  14. 帝国时代3亚洲王朝怎么控制军营训练其它文明的兵种
  15. [计算机视觉] (三)相机的针孔成像模型
  16. sudo: no tty present and no askpass program specified 处理
  17. Python最佳学习路线图
  18. CSAPP 3e Attack lab
  19. Linux:selinux安全上下文
  20. 校外培训机构被叫停,中止营业,这属于营业中断险的保障范围吗?

热门文章

  1. 当计算机运行死机时 可按,2012年会计电算化考试-理论题及答案I套
  2. Linux- Top命令查看系统资源状况
  3. python中pygame增加对话框_在pygame中制作弹出窗口与pgu
  4. 基础 | 如何通过DCGAN实现动漫人物图像的自动生成?
  5. 专访支付宝首席架构师程立
  6. AGV小车典型设计算法及应用
  7. 基于FPGA的千兆以太网的实现(1)
  8. 星座运势sdk代码示例实现分享
  9. 妙趣横生的算法 C语言实现 pdf
  10. iTOP-iMX6开发板-MiniLinux-Max485测试使用文档