服务计算作业三——实现selpg


作业要求

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

提示:

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

实现

由于已经提供了c语言的源代码selpg.c,因此只需要读懂该源代码并将其翻译成go语言程序即可。

根据提示我们需要用到pflag,因此先要得到pflag的包。

go get github.com/spf13/pflag

C语言源代码中定义了一个类:
在这里插入图片描述
将其转化为go语言

type selpg_args struct {//go-lint一直要求变量使用驼峰命名法,但是这里为了便于查看就保留了c中的命名,后面的变量也尽量使用驼峰命名法
	startPage  int
	endPage    int
	inFilename string
	pageLen    int
	pageType   string

	printDest string
}

main&&usage

先把比较简单的main函数和usage函数实现:

func main() {
	arg := new(selpg_args)
	Init(arg)	//利用pflag将arg中各个变量绑定

	check(arg)	//检查变量是否合法

	process_input(arg)	//读取、写入相应文件
}
func usage() {	//输入不合法的时候的提示
	fmt.Fprintf(os.Stderr, "Usage of %s:", os.Args[0])
	fmt.Fprintf(os.Stderr, "-s(start_page) -e(end_page) [-f | -l(lines_per_page)] [-d(dest)] [in_filename]")
	pflag.PrintDefaults()
}

init

init函数是将arg中的各个变量与参数绑定便于输入,不必像c语言一样一个个将参数提取出来。其中5个参数的意义依次如下:要绑定的变量,变量的名称,符号,默认值,对名称的解释。
一开始按照其中一个博客的介绍直接将page type直接与l绑定,发现输入包含-f时会出问题
在这里插入图片描述
最终在另一个博客中找到了解决的方法:引入exist_f,通过exist_f可以判断是否存在f变量。

func Init(arg *selpg_args) {
	var exist_f bool
	pflag.BoolVarP(&exist_f, "type", "f", false, "type of print")
	pflag.Usage = usage
	pflag.IntVarP(&(arg.startPage), "start", "s", -1, "start page")
	pflag.IntVarP(&(arg.endPage), "end", "e", -1, "end page")
	pflag.IntVarP(&(arg.pageLen), "line", "l", 72, "page len")
	pflag.StringVarP(&(arg.printDest), "destionation", "d", "", "print destionation")
	//pflag.StringVarP(&(args.pageType), "type", "f", "l", "type of print")

	pflag.Parse()

	othersArg := pflag.Args()
	fmt.Println(othersArg)
	if len(othersArg) > 0 {
		arg.inFilename = othersArg[0]
	} else {
		arg.inFilename = ""
	}

	if exist_f {
		arg.pageType = "f"
	} else {
		arg.pageType = "l"
	}
}

check

check函数是要判断各个变量是否符合要求,或者是否越界之类的函数,如果有不符合的打印出对应错误信息,并停止运行。

	if arg.startPage < 0 || arg.startPage > math.MaxInt32-1 {
		fmt.Fprintf(os.Stderr, "ERROR: start page invaild.\n")
		usage()
		os.Exit(1)
	}
	if arg.endPage < 0 || arg.endPage > math.MaxInt32-1 || arg.endPage < arg.startPage {
		fmt.Fprintf(os.Stderr, "ERROR: end page invaild.\n")
		usage()
		os.Exit(2)
	}
	if arg.pageLen < 0 || arg.pageLen > math.MaxInt32 {
		fmt.Fprintf(os.Stderr, "ERROR: page len invaild.\n")
		usage()
		os.Exit(3)
	}
	if !(arg.pageType == "f" || arg.pageType == "l") {
		fmt.Fprintf(os.Stderr, "ERROR: page type invaild.\n")
		usage()
		os.Exit(4)
	}

process_input

(涉及文件操作,在打开文件时都要判断是否打开成功,如果不成功则要报错并停止运行)
先判断输入中有没有要读取的文件,如果有则打开,如果没有则从stdin中获取;然后判断有没有输出文件,类似。

	var fin *os.File
	if arg.inFilename != "" {
		var opErr error
		fin, opErr = os.Open(arg.inFilename)
		if opErr != nil {
			fmt.Fprintf(os.Stderr, "\nERROR! Can not open the input file: %s\n", arg.inFilename)
			os.Exit(9)
		}
	} else {
		fin = os.Stdin
	}
	finBuffer := bufio.NewReader(fin)
if arg.printDest != "" {
		cmd = exec.Command("lp", "-d", arg.printDest)
		var desErr error
		cmd.Stdout, desErr = os.OpenFile(arg.printDest, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
		fout, desErr = cmd.StdinPipe()
		if desErr != nil {
			fmt.Fprintf(os.Stderr, "\nERROR! Can not open pipe to \"lp -d%s\"\n", arg.printDest)
			os.Exit(5)
		}
		cmd.Start()
		cmd.Wait()
	} else {
		fout = os.Stdout
	}

然后是根据l或者f输出。
如果是l类型,则一页的行数是pageLen,直接将c语言转为go即可。
在这里插入图片描述

if arg.pageType == "l" {
		lineCtr := 0
		pageCtr := 1
		for {
			line, crc := finBuffer.ReadString('\n')
			if crc != nil {
				break
			}
			lineCtr++
			if lineCtr > arg.pageLen {
				pageCtr++
				lineCtr = 1
			}

			if (pageCtr >= arg.startPage) && (pageCtr <= arg.endPage) {
				_, err := fout.Write([]byte(line))
				if err != nil {
					fmt.Fprintf(os.Stderr, "Write ERROR!")
					os.Exit(6)
				}
			}
		}
	}

如果是f类型,则以换页符’\f’为换页分界。

else {
		pageCtr := 1
		for {
			page, crc := finBuffer.ReadString('\f')
	
			if crc != nil {
				//fmt.Println(page)
				break
			}
			pageCtr++
			if (pageCtr >= arg.startPage) && (pageCtr <= arg.endPage) {
				_, err := fout.Write([]byte(page))
				if err != nil {
					fmt.Fprintf(os.Stderr, "Write ERROR!")
					os.Exit(7)
				}
			}	
		}
}

结果测试

测试文件:
在这里插入图片描述

  1. $ selpg -s1 -e1 input_file(为了能看出效果就加了个-l2)
    在这里插入图片描述

  2. $ other_command | selpg -s10 -e20
    在这里插入图片描述

  3. $ selpg -s10 -e20 input_file >output_file
    在这里插入图片描述
    在这里插入图片描述

  4. $ selpg -s10 -e20 input_file 2>error_file
    在这里插入图片描述
    出现err.txt
    在这里插入图片描述

  5. $ selpg -s10 -e20 input_file >output_file 2>error_file
    在这里插入图片描述
    在这里插入图片描述
    err.txt仍为空。
    如果将命令换成:
    在这里插入图片描述
    则err.txt:
    在这里插入图片描述

  6. $ selpg -s10 -e20 input_file >output_file 2>/dev/null
    之前为了方便是直接在Windows上做的,但是/dev/null是Linux才有的,因此换到Linux上:
    在这里插入图片描述
    error已经被丢弃

  7. $ selpg -s10 -e20 input_file | other_command
    在这里插入图片描述

  8. $ selpg -s10 -e20 -f input_file
    在这里插入图片描述
    (-l命令因为前面一直在用,因此就没有再进行测试)

测试程序

将c源代码编译成可执行文件a.out放入文件夹,然后通过test比较a.out和selpg的输出从而进行测试,这里只做了一条命令的测试,其他同理的:

func TestSelpg(t *testing.T) {
	stdout, err := exec.Command("bash", "-c", "./selpg -s1 -e1 t.txt").Output()
	stdout2, err2 := exec.Command("bash", "-c", "./a.out -s1 -e1 t.txt").Output()
	if err != err2 {
		t.Error(err)
	}
	//fmt.Println(stdout)
	//fmt.Println(stdout2)
	for i := 0; i < len(stdout); i++ {
		if stdout[i] != stdout2[i] {
			t.Error("not fit")
			break
		}
	}
	if err != nil {
		t.Error(err)
	}
}

在这里插入图片描述

源代码

GitHub

参考资料

服务计算——Go语言实现selpg
用Go语言实现selpg指令
服务计算第五周作业——开发简单 CLI 程序
Golang开发 Linux 命令行实用程序——selpg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值