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

18342138 郑卓民

本次作业gitee仓库链接(完整代码)

概述

CLI(Command Line Interface)实用程序是Linux下应用开发的基础。正确的编写命令行程序让应用与操作系统融为一体,通过shell或script使得应用获得最大的灵活性与开发效率。例如:

  • Linux提供了cat、ls、copy等命令与操作系统交互;
  • go语言提供一组实用程序完成从编码、编译、库管理、产品发布全过程支持;
  • 容器服务如docker、k8s提供了大量实用程序支撑云服务的开发、部署、监控、访问等管理任务;
  • git、npm等也是大家比较熟悉的工具。

尽管操作系统与应用系统服务可视化、图形化,但在开发领域,CLI在编程、调试、运维、管理中提供了图形化程序不可替代的灵活性与效率。

开发实践

使用 golang 开发 开发 Linux 命令行实用程序 中的 selpg

提示:

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

基础知识

参考开发 Linux 命令行实用程序

selpg 允许用户指定从输入文本抽取的页的范围,这些输入文本可以来自文件或另一个进程。selpg 是以在 Linux 中创建命令的事实上的约定为模型创建的,这些约定包括:

  • 独立工作
  • 在命令管道中作为组件工作(通过读取标准输入或文件名参数,以及写至标准输出和标准错误)
  • 接受修改其行为的命令行选项

该实用程序从标准输入或从作为命令行参数给出的文件名读取文本输入。它允许用户指定来自该输入并随后将被输出的页面范围。例如,如果输入含有 100 页,则用户可指定只打印第 35 至 65 页。这种特性有实际价值,因为在打印机上打印选定的页面避免了浪费纸张。另一个示例是,原始文件很大而且以前已打印过,但某些页面由于打印机卡住或其它原因而没有被正确打印。在这样的情况下,则可用该工具来只打印需要打印的页面。

实现思路

代码结构

根据上面基础知识的程序运行逻辑描述,可以大概知道实现该程序需要三个主要的函数:

  1. main:解析命令参数的入口函数
  2. processArgs:处理参数,错误检测
  3. processInput:根据命令进行操作

Selpg的使用命令为Usage: selpg [-s startPage] [-e endPage] [-l linesPerPage | -f] [-d printDest] [filename]

参数分别是:开始页码-s、结束页码-e、(自定页长-l 或 遇换页符换页-f)、输出地址-d、输入文件名。

其中开始页码和结束页码是必须的。

将上述命令的参数列表转化为一个数据结构来存储。

type selpgArgs struct {startPage  intendPage    intpageLen    intpageType   boolinFileName stringprintDest  string
}

实验准备

需要安装pflag包:

使用命令:go get github.com/spf13/pflag

代码实现

获取输入参数

此部分主要利用了pflag包,在上面的实验准备中,已经安装了该包。

主要利用了pflag中的IntVarP、StringVarP、BoolVarP、Parse、Args函数。

通过调用pflag.Args()来获得未定义但输入了的参数如文件名。

因此获取输入参数的函数可实现如下:

func getArgs(args *selpgArgs) {// 获取-s -e -l -f -d 参数pflag.IntVarP(&(args.startPage), "startPage", "s", -1, "Define startPage")pflag.IntVarP(&(args.endPage), "endPage", "e", -1, "Define endPage")pflag.IntVarP(&(args.pageLen), "pageLength", "l", 72, "Define pageLength")pflag.StringVarP(&(args.printDest), "printDest", "d", "", "Define printDest")pflag.BoolVarP(&(args.pageType), "pageType", "f", false, "Define pageType")pflag.Parse()// 获取 filename 参数filename := pflag.Args()if len(filename) > 0 {args.inFileName = string(filename[0])} else {args.inFileName = ""}
}

参数错误检测

主要的规则如下:

  • 开始页和结束页是必须要有的参数。
  • 开始页和结束页不得小于等于零,且开始页不能大于结束页
  • -l和-f参数不能同时输入
  • 自定页长不得小于等于0
func checkArgs(args *selpgArgs) {// 判断输入参数合法性if (args.startPage == -1) || (args.endPage == -1) {fmt.Fprintf(os.Stderr, "[Error]The startPage and endPage can't be empty!\n")os.Exit(1)} else if (args.startPage <= 0) || (args.endPage <= 0) {fmt.Fprintf(os.Stderr, "[Error]The startPage and endPage can't be less than 1!\n")os.Exit(2)} else if args.startPage > args.endPage {fmt.Fprintf(os.Stderr, "[Error]The startPage can't be bigger than the endPage!\n")os.Exit(3)} else if (args.pageType == true) && (args.pageLen != 72) {fmt.Fprintf(os.Stderr, "[Error]The command -l and -f are exclusive!\n")os.Exit(4)} else if args.pageLen <= 0 {fmt.Fprintf(os.Stderr, "[Error]The pageLen can't be less than 1 !\n")os.Exit(5)} else {// 输入参数均合法,判断是选择了-l还是-f,并输入参数列表。pageType := "page length."if args.pageType == true {pageType = "The end sign /f."}fmt.Printf("[ArgsStart]\n")fmt.Printf("startPage: %d\nendPage: %d\ninputFile: %s\npageLength: %d\npageType: %s\nprintDestation: %s\n[ArgsEnd]\n", args.startPage, args.endPage, args.inFileName, args.pageLen, pageType, args.printDest)}
}

执行命令

完成处理参数任务后,可以根据输入参数执行任务。

首先根据有无输入的filename参数,判断是从标准输入中获取还是从对应文件名的文件中读取。

打开文件的过程要判断是否打开成功。

最后判断是否有-d参数,以此决定是直接在标准输出中输出还是在特定的位置输出。

如果包含了-d参数,则需要利用到了os/exec包。exec包执行外部命令,它将os.StartProcess进行包装使得它更容易映射到stdin和stdout,并且利用pipe连接i/o。

func excuteCMD(args *selpgArgs) {var inp *os.Fileif args.inFileName == "" { // 从标准输入中读取inp = os.Stdin} else { // 从文件中读取// 检测文件是否可读取checkFileAccess(args.inFileName)var err errorinp, err = os.Open(args.inFileName)// 检测文件是否打开成功checkError(err, "input file")}if len(args.printDest) == 0 { // 输出到标准输出output(os.Stdout, inp, args.startPage, args.endPage, args.pageLen, args.pageType)} else { // 输出到文件中output(getDesio(args.printDest), inp, args.startPage, args.endPage, args.pageLen, args.pageType)}
}func checkError(err error, object string) {if err != nil {fmt.Fprintf(os.Stderr, "[Error]%s:", object)panic(err)}
}func checkFileAccess(filename string) {_, errFileExits := os.Stat(filename)// 检查是否不存在此文件if os.IsNotExist(errFileExits) {fmt.Fprintf(os.Stderr, "[Error]: input file \"%s\" does not exist\n", filename)os.Exit(6)}
}func getDesio(printDest string) io.WriteCloser {cmd := exec.Command("lp", "-d"+printDest)// 将命令行的输入管道stdinpipe获取到的指针赋给foutfout, err := cmd.StdinPipe()checkError(err, "StdinPipe")cmd.Stdout = os.Stdoutcmd.Stderr = os.StderrerrStart := cmd.Run()checkError(errStart, "CMD run")// 将fout返回给output函数作为输出地址参数return fout
}

输出操作

output函数按参数的要求读取特定内容并输出到指定地方中。

此处利用了bufio包,该包实现了带缓存的IO操作。

func output(fout interface{}, inp *os.File, pageStart int, pageEnd int, pageLen int, pageType bool) {lineCount := 0pageCount := 1buf := bufio.NewReader(inp)for true {var line stringvar err errorif pageType {// -f的情况line, err = buf.ReadString('\f')pageCount++} else {// -l的情况line, err = buf.ReadString('\n')lineCount++if lineCount > pageLen {pageCount++lineCount = 1}}if err == io.EOF { // 已经读完break}// 检测读取有无出错checkError(err, "file read in")// 判断是不是在需要输出的内容的范围内if (pageCount >= pageStart) && (pageCount <= pageEnd) {var outputErr error// 通过类型断言,判断fout的类型,知道应该调用哪个函数if stdOutput, ok := fout.(*os.File); ok {_, outputErr = fmt.Fprintf(stdOutput, "%s", line)} else if pipeOutput, ok := fout.(io.WriteCloser); ok {_, outputErr = pipeOutput.Write([]byte(line))} else {fmt.Fprintf(os.Stderr, "[Error]:fout type error.")os.Exit(7)}// 检测输出有无出错checkError(outputErr, "Error happend when output the pages.")}}if pageCount < pageStart { // 开始页太大fmt.Fprintf(os.Stderr, "[Error]: startPage (%d) greater than total pages (%d)\n", pageStart, pageCount)os.Exit(8)} else if pageCount < pageEnd { // 结束页太大fmt.Fprintf(os.Stderr, "[Error]: endPage (%d) greater than total pages (%d)\n", pageEnd, pageCount)os.Exit(9)}
}

main函数编写

func main() {var args selpgArgsgetArgs(&args) // 读取checkArgs(&args) // 判断合法excuteCMD(&args) // 执行任务
}

文件组织

在工作区中创建myselpg文件夹并在此文件夹下创建go文件与输入txt文件

其中input_file.txt中的内容为:

注意:^L 是ascii 0x0C ‘\f’, 换页控制符。使用vim编辑器ctrl+L输入。

单元测试

可以编写一些简单的测试来查看是否达到预期的效果,下面展示一个简单的testing,可创建更多的testing来验证功能。

程序测试

  • ./selpg -s1 -e1 input_file.txt

  • ./selpg -s1 -e1 < input_file.txt

  • ./selpg -s1 -e1 input_file.txt >output_file

  • ./selpg -s1 -e4 input_file.txt 2>error_file

  • ./selpg -s1 -e2 input_file.txt >output_file 2>error_file

  • ./selpg -s1 -e2 -f input_file.txt

  • ./selpg -s1 -e2 -dlp1 input_file.txt

由于没有打印机,该命令正常执行直到发现没有打印机为止。

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

  1. 服务计算 HW3 CLI 命令行实用程序开发基础

    文章目录 一. 概述 二. Golang 的支持 ① 使用 os 包处理参数 ② 使用 flag 包处理参数 三. 开发实践 ① 项目要求 ② 设计说明 1) selpg 定义 2) 代码设计 1. ...

  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. 服务计算作业二——GO语言TDD实践报告

    服务计算作业二--GO语言TDD实践报告 服务计算作业二--GO语言TDD实践报告 教程学习 为一个重复字符五次的函数编写测试,并先使用最少的代码让失败的测试先跑起来(核心概念) 把代码补充完整,使得 ...

  6. 网工必须了解的华为华三设备基础命令行与WEB界面

    华为 华三操作系统 华为VRP (Versatile Routing     Platform),通用路由平台,华为企业/运营商产品都使用该系统,目前主流的是VRP5(企业产品),数据中心/运营商产品 ...

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

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

  8. 服务计算作业:Docker 容器技术

    Docker实践:CaaS 服务计算作业:Docker 容器技术 准备docker环境(win10) 运行第一个容器:hello-world 运行镜像 显示本地镜像库内容 Mysql安装 构建镜像并运 ...

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

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

最新文章

  1. AI一分钟 | Yann LeCun怒批机器人Sophia:招摇撞骗;李嘉诚:我比较保守,只投了1亿港币到比特币终端市场
  2. Https单向认证和双向认证介绍
  3. 学会转变你的思维方式,一切都会变得不一样
  4. sklearn模型的训练(上)
  5. Django日志信息路径的设置
  6. pytorch tensor 初始化_PyTorch简明笔记[1]-Tensor的初始化和基本操作
  7. OkHttp上传Json嵌套对象
  8. 使用 CODING 进行 Spring Boot 项目的集成
  9. 三、Springmvc之Controller层方法返回值
  10. python是什么 自学-怎么自学python,大概要多久?
  11. 现控笔记(五)稳定性与Lyapunov方法
  12. 6. JavaScript String 对象
  13. CentOS下安装JDK7
  14. python中的Rotation常见报错问题
  15. Android开源项目以及开源库集合(持续更新中)
  16. 他一年开发19款!款款口碑爆棚
  17. 简单游戏的c语言程序,[C语言编写小程序]简单打飞碟游戏
  18. iOS自动打包,并上传蒲公英
  19. 美服测试服无法连接验证服务器,美服1.10 test服务器开了,我的一些测试心得
  20. 快速查询苹果手机的UUID

热门文章

  1. windows10 PHP+Apache安装配置教程
  2. STM32F407单片机通用24CXXX读写程序(KEIL),兼容24C系列存储器(24C01到24C512),支持存储器任意地址跨页连续读写多个页
  3. Solver lbfgs supports only 'l2' or 'none' penalties, got l1 penalty.解决办法
  4. 海森矩阵与多元多项式的结合与极值判定【浅显易懂版:欢迎补充】
  5. 做好权益设置,打造有价值的用户运营体系
  6. 计算机电缆介绍,阻燃计算机电缆介绍
  7. iis 反向代理 应用程序_我如何反向工程字节并创建自己的字节Web应用程序
  8. 混合现实开发_20种增强和混合现实应用的想法和灵感
  9. 关于CDN的IP隐藏以及对服务器安全的帮助
  10. 什么是面向对象?什么是面向过程?及其优缺点。