CLI 命令行实用程序开发基础

CLI 命令行实用程序开发基础

概述

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

实现

selpg 程序逻辑

selpg 是从文本输入选择页范围的实用程序。该输入可以来自作为最后一个命令行参数指定的文件,在没有给出文件名参数时也可以来自标准输入。
selpg 首先处理所有的命令行参数。在扫描了所有的选项参数(也就是那些以连字符为前缀的参数)后,如果 selpg 发现还有一个参数,则它会接受该参数为输入文件的名称并尝试打开它以进行读取。如果没有其它参数,则 selpg 假定输入来自标准输入。

参数处理

“-sNumber”和“-eNumber”强制选项:
selpg 要求用户用两个命令行参数“-sNumber”(例如,“-s10”表示从第 10 页开始)和“-eNumber”(例如,“-e20”表示在第 20 页结束)指定要抽取的页面范围的起始页和结束页。这两个选项,“=sNumber”和“-eNumber”是强制性的,而且必须是命令行上在命令名 selpg 之后的头两个参数:

$ selpg -s10 -e20 ...

“-lNumber”和“-f”可选选项:
selpg 可以处理两种输入文本:

  • 类型 1:该类文本的页行数固定。这是缺省类型,因此不必给出选项进行说明。也就是说,如果既没有给出“-lNumber”也没有给出“-f”选项,则 selpg 会理解为页有固定的长度(每页 72 行)。选择 72 作为缺省值是因为在行打印机上这是很常见的页长度。这样做的意图是将最常见的命令用法作为缺省值,这样用户就不必输入多余的选项。该缺省值可以用“-lNumber”选项覆盖,如下所示:
    $ selpg -s10 -e20 -l66 ...
    
  • 类型 2:该类型文本的页由 ASCII 换页字符(十进制数值为 12,在 C 中用“\f”表示)定界。该格式与“每页行数固定”格式相比的好处在于,当每页的行数有很大不同而且文件有很多页时,该格式可以节省磁盘空间。在含有文本的行后面,类型 2 的页只需要一个字符 ― 换页 ― 就可以表示该页的结束。
    类型 2 格式由“-f”选项表示,如下所示:
    $ selpg -s10 -e20 -f ...
    

“-dDestination”可选选项:
selpg 还允许用户使用“-dDestination”选项将选定的页直接发送至打印机。这里,“Destination”应该是 lp 命令“-d”选项(请参阅“man lp”)可接受的打印目的地名称。

$ selpg -s10 -e20 -dlp1
代码实现
//定义保存参数数据的结构体
type selpgArgs struct {  
	startPage  int
	endPage    int
	inFileName string
	pageLen    int
	pageType   bool
	printDest  string
}

//输入参数使用 github.com/spf13/pflag 包提供的pflag进行处理
func getArgs(args *selpgArgs) {

	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()

	argLeft := pflag.Args()
	if len(argLeft) > 0 {
		args.inFileName = string(argLeft[0])
	} else {
		args.inFileName = ""
	}
}

checkArgs函数

  • 先检查开始页args.startPage和结束页args.endPage是否被赋值,然后检查开始页args.startPage和结束页args.endPage是否为正数;
  • 然后检查开始页args.startPage是否大于结束页args.endPage;检查自定页长-l和遇换页符换页-f是否同时出现。
  • 最后判断当自定页长-l出现时args.pageLen是否小于1。遇到不合规的地方正常结束程序,全部合规则输出得到的参数。
//命令行获取之参数后,先进行参数检查以避免参数错误。
//如果出现错误则将问题输出并正常结束程序。
//如果参数正确则把正确参数值输出到屏幕上。
func checkArgs(args *selpgArgs) {

	if (args.startPage == -1) || (args.endPage == -1) {
		fmt.Fprintf(os.Stderr, "\n[Error]The startPage and endPage can't be empty! Please check your command!\n")
		os.Exit(2)
	} else if (args.startPage <= 0) || (args.endPage <= 0) {
		fmt.Fprintf(os.Stderr, "\n[Error]The startPage and endPage can't be negative! Please check your command!\n")
		os.Exit(3)
	} else if args.startPage > args.endPage {
		fmt.Fprintf(os.Stderr, "\n[Error]The startPage can't be bigger than the endPage! Please check your command!\n")
		os.Exit(4)
	} else if (args.pageType == true) && (args.pageLen != 72) {
		fmt.Fprintf(os.Stderr, "\n[Error]The command -l and -f are exclusive, you can't use them together!\n")
		os.Exit(5)
	} else if args.pageLen <= 0 {
		fmt.Fprintf(os.Stderr, "\n[Error]The pageLen can't be less than 1 ! Please check your command!\n")
		os.Exit(6)
	} else {
		pageType := "page length."
		if args.pageType == true {
			pageType = "The end sign /f."
		}
		fmt.Printf("\n[ArgsStart]\n")
		fmt.Printf("startPage: %d\nendPage: %d\ninputFile: %s\npageLength: %d\npageType: %s\nprintDestation: %s\n[ArgsEnd]", args.startPage, args.endPage, args.inFileName, args.pageLen, pageType, args.printDest)
	}

}

//检查结束之后,开始调用excuteCMD函数执行命令。
func checkError(err error, object string) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "\n[Error]%s:", object)
		panic(err)
	}
}

在excuteCMD函数中:
1、检查输入。如果没有给定文件名,则从标准输入中获取;如果给出读取的文件名,则调用函数checkFileAccess检查文件是否存在。
2、打开文件,使用函数checkError检查是否出现错误。如果打开出错则输出错误并抛出恐慌。
3、判断是否有-d参数。如果没有-d参数,选择的页直接从os.Stdout标准输出中输出。如果-d存在,则从指定的打印通道中输出。

func excuteCMD(args *selpgArgs) {
	var fin *os.File
	if args.inFileName == "" {
		fin = os.Stdin
	} else {
		checkFileAccess(args.inFileName)
		var err error
		fin, err = os.Open(args.inFileName)
		checkError(err, "File input")
	}

	if len(args.printDest) == 0 {
		output2Des(os.Stdout, fin, args.startPage, args.endPage, args.pageLen, args.pageType)
	} else {
		output2Des(cmdExec(args.printDest), fin, args.startPage, args.endPage, args.pageLen, args.pageType)
	}
}

func checkFileAccess(filename string) {
	_, errFileExits := os.Stat(filename)
	if os.IsNotExist(errFileExits) {
		fmt.Fprintf(os.Stderr, "\n[Error]: input file \"%s\" does not exist\n", filename)
		os.Exit(7)
	}
}

在-d参数存在时,就需要os/exec包的使用

func cmdExec(printDest string) io.WriteCloser {
    cmd := exec.Command("lp", "-d"+printDest)
    fout, err := cmd.StdinPipe()
    checkError(err, "StdinPipe")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    errStart := cmd.Run()
    checkError(errStart, "CMD run")
    return fout
}

最后再使用输出函数output2Des,将输入的文件,按页码要求读取并输出到fout中。

func output2Des(fout interface{}, fin *os.File, pageStart int, pageEnd int, pageLen int, pageType bool) {

	lineCount := 0
	pageCount := 1
	buf := bufio.NewReader(fin)
	for true {

		var line string
		var err error
		if pageType {
			//If the command argument is -f
			line, err = buf.ReadString('\f')
			pageCount++
		} else {
			//If the command argument is -lnumber
			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
			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, "\n[Error]:fout type error. ")
				os.Exit(8)
			}
			checkError(outputErr, "Error happend when output the pages.")
		}
	}
	if pageCount < pageStart {
		fmt.Fprintf(os.Stderr, "\n[Error]: startPage (%d) greater than total pages (%d), no output written\n", pageStart, pageCount)
		os.Exit(9)
	} else if pageCount < pageEnd {
		fmt.Fprintf(os.Stderr, "\n[Error]: endPage (%d) greater than total pages (%d), less output than expected\n", pageEnd, pageCount)
		os.Exit(10)
	}
}
程序测试

按文档 使用 selpg 章节要求测试你的程序
分别使用命令:

$ selpg -s1 -e1 input_file.txt

在这里插入图片描述

$ selpg -s1 -e1 < input_file.txt

在这里插入图片描述

selpg -s1 -e2 input_file.txt > output_file.txt

在这里插入图片描述
在这里插入图片描述

selpg -s1 -e3 input_file.txt > output_file.txt 2>error_file

在这里插入图片描述

selpg -s1 -e2 -f input_file.txt 

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C 和 C++ 语言都是世界上最流行且使用最普遍的编程语言, 因此 Eclipse 平台(Eclipse Platform)提供对 C/C++ 开发的支持一点都不足为奇。 因为 Eclipse 平台只是用于开发者工具的一个框架,它不直接支持 C/C++;它使用外部插件来提供支持。 本文将向您演示如何使用 CDT — 用于 C/C++ 开发的一组插件。CDT 项目(有关链接, 请参阅本文后面的 参考资料一节)致力于为 Eclipse 平台提供功能完全的 C/C++ 集成开发环境(Integrated Development Environment,IDE)。 虽然该项目的重点是 Linux,但它在可使用 GNU 开发者工具的所有环境(包括 Win32(Win 95/98/Me/NT/2000/XP)、QNX Neutrino 和 Solaris 平台)中都能工作。 CDT 是完全用 Java 实现的开放源码项目(根据 Common Public License 特许的),它作为 Eclipse SDK 平台的一组插件。这些插件将 C/C++ 透视图添加到 Eclipse 工作台(Workbench)中, 现在后者可以用许多视图和向导以及高级编辑和调试支持来支持 C/C++ 开发。 由于其复杂性,CDT 被分成几个组件,它们都采用独立插件的形式。 每个组件都作为一个独立自主的项目进行运作,有它自己的一组提交者、错误类别和邮件列表。 但是,所有插件都是 CDT 正常工作所必需的。 下面是 CDT 插件/组件的完整列表: 主 CDT 插件(Primary CDT plug-in)是“框架”CDT 插件。 CDT 功能 Eclipse(CDT Feature Eclipse)是 CDT 功能组件(Feature Component)。 CDT 核心(CDT Core)提供了核心模型(Core Model)、CDOM 和核心组件(Core Component)。 CDT UI是核心 UI、视图、编辑器和向导。 CDT 启动(CDT Launch)为诸如编译器和调试器之类的外部工具提供了启动机制。 CDT 调试核心(CDT Debug Core)提供了调试功能。 CDT 调试 UI(CDT Debug UI)为 CDT 调试编辑器、视图和向导提供了用户界面。 CDT 调试 MI(CDT Debug MI)是用于与 MI 兼容的调试器的应用程序连接器。 现在,让我们研究一下如何在实际应用程序中使用这些组件。图 1 显示了 Eclipse 中的 C/C++ 项目: 图 1. 在带有 CDT 插件的 Eclipse 中编辑 C/C++ 项目 安装和运行 CDT 在下载和安装 CDT 之前,首先必需确保 GNU C 编译器(GNU C compiler,GCC)以及所有附带的工具(make、binutil 和 GDB)都是可用的。 如果正在运行 Linux,只要通过使用适用于您分发版的软件包管理器来安装开发软件包。 在 Windows平台上,将需要安装 Cygwin 工具箱(请参阅 参考资料以获得链接)。Cygwin 是用于 Windows 的类 UNIX 环境,它包括 GCC 移植以及所有必需的开发工具,包括 automake 和 GNU 调试器(GNU Debugger,GDB)。Cygwin 是在 cygwin1.dll 库基础上构建的。Cygwin 的备用解决方案是 Minimalist GNU for Windows(MinGW)(请参阅 参考资料以获得链接)。 该工具是一组可免费获取、自由分发的特定于 Windows 的头文件和导入库,这些头文件和导入库与 GNU 工具集(它们允许您生成不依赖于任何第三方 DLL 的本机 Windows 程序)结合在一起。 如果您想要创建与 POSIX 兼容的 Windows 应用程序,那么 MinGW 是最佳选择。MinGW 甚至可以在 Cygwin 安装之上工作。 Solaris和 QNX要求您从因特网下载并安装其特定的 GCC、GNU Make binutils 和 GDB 移植(请参阅 参考资料以获得链接)。 假设您安装了适当的 Java SDK/JRE 和 Eclipse 平台 SDK,并且它们都正常运行。CDT 以两种“方式”可用:稳定的发行版和试运行版(nightly build)。 试运行版未经完全测试,但它们提供了更多的功能并改正了当前错误。 安装之前,请检查磁盘上是否存在先前版本的 CDT,如果存在,请确保完全除去它。 因为 CDT 没有可用的卸载程序,所以需要手工除去它。 为了检查先前版本是否存在,转至 CDT 插件所驻留的目录: eclipse/plugins 。 接着,除

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值