作业要求
使用 golang 开发 开发 Linux 命令行实用程序 中的 selpg
提示:
- 请按文档 使用 selpg 章节要求测试你的程序
- 请使用 pflag 替代 goflag 以满足 Unix 命令行规范, 参考:Golang之使用Flag和Pflag
- golang 文件读写、读环境变量,请自己查 os 包
- “-dXXX” 实现,请自己查 os/exec 库,例如案例 Command,管理子进程的标准输入和输出通常使用 io.Pipe,具体案例见 Pipe
- 请自带测试程序,确保函数等功能正确
实现
由于已经提供了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)
}
}
}
}
结果测试
测试文件:
-
$ selpg -s1 -e1 input_file(为了能看出效果就加了个-l2)
-
$ other_command | selpg -s10 -e20
-
$ selpg -s10 -e20 input_file >output_file
-
$ selpg -s10 -e20 input_file 2>error_file
出现err.txt
-
$ selpg -s10 -e20 input_file >output_file 2>error_file
err.txt仍为空。
如果将命令换成:
则err.txt:
-
$ selpg -s10 -e20 input_file >output_file 2>/dev/null
之前为了方便是直接在Windows上做的,但是/dev/null是Linux才有的,因此换到Linux上:
error已经被丢弃 -
$ selpg -s10 -e20 input_file | other_command
-
$ 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)
}
}
源代码
参考资料
服务计算——Go语言实现selpg
用Go语言实现selpg指令
服务计算第五周作业——开发简单 CLI 程序
Golang开发 Linux 命令行实用程序——selpg