服务计算 第四周 Go语言开发CLI命令实用程序

本文详细介绍使用Go语言开发命令行界面(CLI)实用程序的过程,包括开发环境配置、使用pflag包进行参数解析、程序框架构建及测试方法。特别关注了如何分析指令、设置参数并确保其合法性,以及执行指令的具体流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


1.题目要求

在这里插入图片描述

CLI要求见此


2.使用材料

2.1开发环境

终于,最后还是使用了CentOs7桌面版(真香)。VsCode真的好用。
贴出桌面安装步骤

yum groupinstall "GNOME Desktop"
ln -sf /lib/systemd/system/runlevel5.target /etc/systemd/system/default.target

分别是安装桌面,以及将桌面设置为开机启动。
根据情况加sudo指令
除此之外,还安装了 g++用以生成测试文档代码编写。


2.2包

使用了bufio io os用作读取,输入输出
使用了pflag包。需要注意的是,pflagflag的升级版本,因此需要安装,安装地址为github.com/spf13/pflag

go get github.com/spf13/pflag

直接获取,即可使用,非常快捷。


3.框架构建

3.1编程思路

按照C语言实现selpg的思路来看,还是非常简单的。大致可以分成以下几个步骤:
读取指令 -> 分析指令 -> 设置参数 -> 判断参数合法 -> 执行指令
同样的,我们也可以按照同样的思路来实现:
读取指令 -> 分析指令并设置参数 -> 判断参数的合法性 -> 执行指令


3.2编写框架

指令描述

USAGE: ./selpg [--s start] [--e end] [--l lines | --f ] [ --d dest ] [ in_filename ]

 selpg --s start    : start page
 selpg --e end      : end page
 selpg --l lines    : lines/page
 selpg --f          : check page with '\f'
 selpg --d dest     : pipe destination
  • 由于使用了pflag,指令默认方式变为 --s 20 而不是 -s20

3.2.1结构

首先,我们需要构造我们的结构,根据我们能够使用的参数,设置了几个变量

type args struct {
	start int				//起始页码
	end int					//终止页码
	length int				//行数
	readType string			//‘l’代表按照行数计算页;‘f’为按照换页符计算页
	dest string				//定向位置
	inputType string		//输入方式
}

3.2.2参数绑定

将所有的参数使用pflag绑定到变量上。

	sa := new(args)

	//参数绑定变量
	flag.IntVar(&sa.start, "s", 0, "the start Page")				//开始页码,默认为0
	flag.IntVar(&sa.end, "e", 0, "the end Page")					//结束页码,默认为0
	flag.IntVar(&sa.length, "l", 72, "the length of the page")		//每页行数,默认为72行每页
	flag.StringVar(&sa.dest, "d", "", "the destiny of printing")	//输出位置,默认为空字符

3.2.3分析指令并设置参数

主要分析两个参数:输入方式(文件输入还是键盘输入)、读取方式(l / f 设置每页行数还是以换页符为准)

	//设置读取方式
	isF := flag.Bool("f", false, "")
	flag.Parse()

	//如果输入f,按照f并取-1;否则按照 l
	if *isF {
		sa.readType = "f"
		sa.length = -1
	} else {
		sa.readType = "l"
	}

	//如果使用了文件输入,将方式置为文件名
	sa.inputType = ""
	if flag.NArg() == 1 {
		sa.inputType = flag.Arg(0)
	}

3.2.4判断参数是否合法

判断剩余参数、l/f是否同时出现、起始页与终止页是否冲突

	//检查剩余参数数量
	if narg := flag.NArg(); narg != 1 && flag.NArg() != 0 {
		usage()
		os.Exit(1)
	}
	//检查起始终止页
	if sa.start > sa.end || sa.start < 1 {
		usage()
		os.Exit(1)
	}
	//检查l f 是否同时出现
	if sa.readType == "f" && sa.length != -1 {
		usage()
		os.Exit(1)
	}

3.2.5执行指令

根据参数执行指令
流程为:判断输入方式,并将输入流绑定 -> 如果有管道,绑定管道 -> l/f读取

	//初始化
	fin := os.Stdin					//输入
	fout := os.Stdout				//输出
	currentLine := 0				//当前行
	currentPage := 1				//当前页
	var inpipe io.WriteCloser		//管道
	var err error					//错误

	//判断输入方式
	if sa.inputType != "" {
		fin, err = os.Open(sa.inputType)
		if err != nil {
			fmt.Fprintf(os.Stderr, "ERROR: Can't find input file \"%s\"!\n", sa.inputType)
			//fmt.Println(err)
			usage()
			os.Exit(1)
		}
		defer fin.Close()			//全部结束了再关闭
	}

	//确定输出到文件或者输出到屏幕
	//通过用管道接通grep模拟打印机测试,结果输出到屏幕
	if sa.dest != "" {
		cmd := exec.Command("grep", "-nf", "keyword")
		inpipe, err = cmd.StdinPipe()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		defer inpipe.Close() //最后执行
		cmd.Stdout = fout
		cmd.Start()
	}

	//分页方式
	//设置行数
	if sa.readType == "l" {
		//按照行读取
		line := bufio.NewScanner(fin)
		for line.Scan() {
			if currentPage >= sa.start && currentPage <= sa.end {
				//输出到窗口
				fout.Write([]byte(line.Text() + "\n"))
				if sa.dest != "" {
					//定向到文件管道
					inpipe.Write([]byte(line.Text() + "\n"))
				}
			}
			currentLine++
			//翻页
			if currentLine == sa.length {
				currentPage++
				currentLine = 0
			}
		}
	} else {
		//用换行符 '\f'分页 
		rd := bufio.NewReader(fin)
		for {
			page, ferr := rd.ReadString('\f')
			if ferr != nil || ferr == io.EOF {
				if ferr == io.EOF {
					if currentPage >= sa.start && currentPage <= sa.end {
						fmt.Fprintf(fout, "%s", page)
					}
				}
				break
			}
			//'\f'翻页
			page = strings.Replace(page, "\f", "", -1)
			currentPage++
			if currentPage >= sa.start && currentPage <= sa.end {
				fmt.Fprintf(fout, "%s", page)
			}
		}
	}

3.2.6USAGE

输入错误提示信息,直接写上去Fprintfln即可

	fmt.Fprintf(os.Stderr, "\nUSAGE: ./selpg [--s start] [--e end] [--l lines | --f ] [ --d dest ] [ in_filename ]\n")
	fmt.Fprintf(os.Stderr, "\n selpg --s start    : start page")
	fmt.Fprintf(os.Stderr, "\n selpg --e end      : end page")
	fmt.Fprintf(os.Stderr, "\n selpg --l lines    : lines/page")
	fmt.Fprintf(os.Stderr, "\n selpg --f          : check page with '\\f'")
	fmt.Fprintf(os.Stderr, "\n selpg --d dest     : pipe destination\n")

4测试

4.1生成测试文件

使用了C++来生成文件,具体如下:

/*
	生成一个测试文件
	文件共有500行,每行格式为: line + 行号
	行号从1开始,直到500
	每10行有一个换页符'\f'将行号隔开,因此共有50页
	输出在data.txt文件中
*/
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main () {
	ofstream fout;
	fout.open("data.com");
	string line = "line ";
	string page = "\f";
	for (int i = 0; i < 50; i++) {
		for (int j = 0; j < 10; j++) {
			fout << line << i * 50 + j + 1 << endl;
		}
		fout << page;
	}
}

生成文件

g++ create.cpp
./a.out

4.2样例测试

使用selpg -> 使用selpg部分测试

    1. selpg -s1 -e1 input_file
selpg --s 1 --e 1 data.txt

在这里插入图片描述

在这里插入图片描述

由于过长,中间不显示,之后的截图中不再完全显示,只显示代表片段

    1. selpg -s1 -e1 < input_file
selpg --s 1 --e 1 < data.txt

在这里插入图片描述

    1. selpg -s10 -e20 input_file >output_file

页数有限,取3 - 5

selpg --s 3 --e 5 data.txt > output1.txt 
touch output1.txt
selpg --s 3 --e 5 data.txt >output1.txt
vim output1.txt

打开output1.txt文件

在这里插入图片描述

    1. selpg -s10 -e20 input_file 2>error_file
touch err.txt
selpg --s 10 --e 20 data.txt 2>err.txt
vim err.txt

文件应该放不下,因此打开err.txt

在这里插入图片描述

    1. selpg -s10 -e20 input_file >output_file 2>error_file

同样选取3 - 5

touch output2.txt
selpg --s 3 --5 data.txt >output2.txt 2>err.txt
vim output2.txt
vim err.txt

output2.txt

在这里插入图片描述

    1. selpg -s10 -e20 input_file >output_file 2>/dev/null
touch output3.txt
selpg --s 10 --e 20 data.txt >output3.txt 2>/dev/null
vim output3.txt

丢弃了错误信息

在这里插入图片描述

    1. selpg -s10 -e20 -l66 input_file
  • 为了方便观察,使用l3
selpg --s 10 --e 20 --l 3 data.txt

在这里插入图片描述

    1. selpg -s10 -e20 -dlp1 input_file

为了方便,显示10~15页,每页3行

selpg --s 10 --e 15 --l 3 --d lp1 data.txt

在这里插入图片描述

    1. selpg -s10 -e20 -f input_file

为了方便显示,输出10-12

selpg --s 10 --e 12 --f data.txt

在这里插入图片描述


5源码地址

git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值