服务计算 HW3 CLI 命令行实用程序开发基础
文章目录
- 一. 概述
- 二. 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 命令行实用程序开发基础相关推荐
- 服务计算作业三——CLI 命令行实用程序开发基础
服务计算作业三--CLI 命令行实用程序开发基础 18342138 郑卓民 本次作业gitee仓库链接(完整代码) 概述 CLI(Command Line Interface)实用程序是Linux下应 ...
- 基于Golang的CLI 命令行程序开发
基于Golang的CLI 命令行程序开发 [阅读时间:约15分钟] 一. CLI 命令行程序概述 二. 系统环境&项目介绍&开发准备 1.系统环境 2.项目介绍 3.开发准备 三.具体 ...
- Arduino CLI命令行ESP32开发环境搭建(Linux Ubuntu操作系统)
陈拓2023/03/06-2023/03/11 简介 Arduino cli是一个命令行界面,您可以使用它创建草图(sketch)并将其上传到开发板中.它提供了ArduinoIDE的所有功能: 编写s ...
- 服务计算 - 3 Golang开发Linux命令行实用程序 - selpg
文章目录 Golang开发Linux命令行实用程序 - selpg 1. 介绍 2. 设计与实现 2.1 设计思路 2.2 功能模块划分与实现 3. 参考文献 Golang开发Linux命令行实用程序 ...
- 命令行工具开发:如何快速实现命令行提示?
简介:对于稍微复杂一些的命令行工具,命令行的提示功能必不可少.那么对于不同语言的开发者,有没有一种简单快捷的实现方式呢?本文分享一种快速实现的方法,使用YAML文件定义命令行工具的使用规范,再通过工具 ...
- 使用 CUPS 命令行实用程序设置和管理打印机
使用 CUPS 命令行实用程序设置和管理打印机 本节提供 CUPS 命令的简介并介绍如何设置和管理打印机. CUPS 命令行实用程序 CUPS 提供用于设置打印机以及使网络中的系统可以访问这些打印机的 ...
- 完全命令行.NET开发
很久没写日记了,今天走一个 Windows系统的GUI太好用了,以至于很多Windows开发者都不知道还有更好用的命令行模式.命令行模式可以把自己经常使用的功能写成脚本,一个命令就可以全部工作执行掉, ...
- add-apt-repository:找不到命令_手把手教你使用nodejs编写cli(命令行)
手把手教你使用nodejs编写cli(命令行) 前端日常开发中,会遇见各种各样的cli,比如一行命令帮你打包的webpack,一行命令帮你生成vue项目模板的vue-cli,还有创建react项目的c ...
- 华为(huawei)USG6000的CLI命令行综合配置之Ensp真机连接 USG6000防火墙
文章目录 前言 一.拓扑图及拓扑说明 二.配置步骤及验证 1.配置真机连接USG6000 前言 华为的USG系列防火墙一般部署在园区网出口中,很多朋友对USG系列如何进行配置不是太了解.本案例将用en ...
最新文章
- CB:南土所梁玉婷组-细菌群落的高稳定性和代谢能力促进了土壤中易分解碳的快速减少...
- as3 内容自适应容器大小
- 经验 | 深度学习中从基础综述、论文笔记到工程经验、训练技巧
- Linux 获取屏幕分辨率与窗口行列数(c/c++)
- H5炫酷特效系列1——canvas满屏幕变换爱心示例
- 中国数字绘图板行业市场供需与战略研究报告
- Q85:对比“直接光照”和“间接光照”的反射模型
- 利用HTML制作简易新闻网站的静态网页
- 菜鸟的MySQL学习笔记(二)
- 使用 Java 操作 Kubernetes API
- php unexpected t_if,zblog提示syntax error, unexpected 'if' (T_IF)出错,模版压缩导致PHP提示代码错误...
- Mac10.14版本安装虚拟机Parallels Desktop 14和windows7 64位旗舰版镜像
- 人才测评之计划能力,如何提高计划能力?
- 帝国时代3亚洲王朝怎么控制军营训练其它文明的兵种
- [计算机视觉] (三)相机的针孔成像模型
- sudo: no tty present and no askpass program specified 处理
- Python最佳学习路线图
- CSAPP 3e Attack lab
- Linux:selinux安全上下文
- 校外培训机构被叫停,中止营业,这属于营业中断险的保障范围吗?