服务计算 第四周
Go语言开发CLI 命令行实用程序
1.题目要求
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
包。需要注意的是,pflag
是flag
的升级版本,因此需要安装,安装地址为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
部分测试
-
- selpg -s1 -e1 input_file
selpg --s 1 --e 1 data.txt
由于过长,中间不显示,之后的截图中不再完全显示,只显示代表片段
-
- selpg -s1 -e1 < input_file
selpg --s 1 --e 1 < data.txt
-
- 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文件
-
- 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
-
- 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
-
- 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
丢弃了错误信息
-
- selpg -s10 -e20 -l66 input_file
- 为了方便观察,使用
l3
selpg --s 10 --e 20 --l 3 data.txt
-
- selpg -s10 -e20 -dlp1 input_file
为了方便,显示
10~15
页,每页3行
selpg --s 10 --e 15 --l 3 --d lp1 data.txt
-
- selpg -s10 -e20 -f input_file
为了方便显示,输出
10-12
页
selpg --s 10 --e 12 --f data.txt