Selpg.go
此文档用于说明Go语言的CLI程序的完成过程以及相关测试, 接下来我将按照写程序的顺序来依次介绍每个部分的过程。
对包的引用
写这个CLI程序的过程中,需要用到的包有下面这些,在代码注释中,我对这些包进行了详细的解释:
import (
"bufio" // 标准输入流和数据之间的交互 //
"fmt" // 引用io.EOF 来判断错误是否是文件尾导致,同时它还包含有格式化I/O函数,具有输入输出功能 //
"io" // 用于将错误信息写入到标准错误流,io包提供了对I/O原语的基本接口 //
"log" // 书写错误信息 //
"os" // 用于打开文件和异常退出时发送状态码 //
"os/exec" // 开启lp子进程 //
"strings" // 划分、拼接字符串 //
flag "github.com/spf13/pflag" // 获取程序运行时用户输入的参数和标识 //
)
你可以把对包的引用看作是c++里面对函数库的引用,它位于一个程序的开头。然后接下来就开始书写我们的程序了
变量的初始化以及定义过程
先贴上初始化的有关代码
type selpgargs struct {
start_page int
end_page int
input_file string
destination string
page_len int
form_deli bool
}
startNum := flag.IntP("startpage", "s", 0, "page to start")
endNum := flag.IntP("endpage", "e", 0, "page to end")
lineNum := flag.IntP("linenumber", "1", 72, "a page will consist of a fixed number of lines")
forcePage := flag.BoolP("forcepaging", "f", false, "Change page only if '-f' appears")
dstPrinter := flag.StringP("destination", "d", "", "Choose a printer to accept the result as a task")
l := log.New(os.Stderr, "", 0)
bytes := make([]byte,65535)
var data string
var resultData string
flag.Parse()
我们来逐一的解释一下上面这些有关初始化的代码。首先是从startNum到dstPrinter这五个变量的声明和初始化,这些变量的含义和它们变量名表面的意思一样,是我们对于 pflag 中各个标识的设置和变量绑定。
然后接着分别是对报错输入输出流的绑定,缓冲区的创建以及读入数据变量和结果数据变量的创建。最后一句,flag.Parse()是用来解析上面初始化的变量的。
检查标识的合法性
if *startNumber == 0 || *endNumber == 0
{
l.Println("Necessary falgs are not given!")
flag.Usage() // 用于输出所有定义了的命令行参数和帮助信息. 一般,当命令行参数解析出错时,该函数会被调用。
os.Exit(1)
}
if(*startNumber > *endNumber) || *startNumber < 0 || *endNumber < 0 || *lineNumber <= 0
{
l.Println("Invalid flag values!")
flag.Usage()
os.Exit(1)
}
if *lineNumber != 72 && *forcePage {
l.Println("Linenumber and forcepaging cannot be set at the same time!")
flag.Usage()
os.Exit(1)
}
if flag.NArg() > 1 {
l.Println("Too many arguments!")
flag.Usage()
os.Exit(1)
}
这一段检测合法性的代码可以分为四段,这四段分别检测了以下四个方面的合法性:
- -s和-e是否有被设置
代码上体现为判断s和e的值是否为0.因为s和
e在初始化的时候,值是被初始化为0了,所以通过判断它们的值是否为0就可以判断出它们是否有被设置- 标识的值是否合法
如果起始页的数字大于结束页的数字或者和页有关的参数小于零的话,都是不合法的- 命令中是否同时出现一个参数位上的两个参数
这里主要就检测换行符和换页符参数是否有同时出现- 参数数量是否太多
pflag里的NArg方法,检测未预定义参数的个数
如果这一段代码有检测到任何不符合要求的参数值或者不合理的逻辑的话,程序就会通过上面初始化的标准错误流输出对应的错误信息,打印正确的使用方法并退出程序。
读取输入数据
if flag.NArg() == 0 {
/* 参数中没有能够按照预定义的参数解析的部分,通过flag.Args()即可获取,是一个字符串切片 */
reader := bufio.NewReader(os.Stdin)
size, err := reader.Read(bytes)
for size != 0 && err == nil {
data = data + string(bytes)
size, err = reader.Read(bytes)
}
// Error
if err != io.EOF {
l.Println("Error occured when reading from StdIn:\n", err.Error())
os.Exit(1)
}
} else {
file, err := os.Open(flag.Args()[0])
if err != nil {
l.Println("Error occured when opening file:\n", err.Error())
os.Exit(1)
}
// 读取整个文件
size, err := file.Read(bytes)
for size != 0 && err == nil {
data = data + string(bytes)
size, err = file.Read(bytes)
}
if err != io.EOF {
l.Println("Error occured when reading file:\n", err.Error())
os.Exit(1)
}
}
这里我们的目标有两个,第一个是判断输入的方式,第二个是将数据读入到上面我们所创建的数据变量data中。
输入的方式有两种,一种是标准输入的模式,一种是文件输入的方式。
对于标准输入的模式来说,是没有额外的参数的。bufio.NewReader(os.stdin)创建了一个reader并绑定到了标准输入流上面,我们让这个reader去读取缓冲区bytes的数据,并将读入的数据转换成string再传到数据变量data里面。当然,如果这个环节出现了错误的话,程序会报出相关的错误信息,并输出相应的错误状态。
对于文件输入的模式来说,携带了一个额外的参数,使用os.Open()打开文件,正常情况下,我们就通过file.Read()迭代的读出所有的文件内容。
这个过程正常结束之后,我们就把要读取的内容全部保存在data里面了
输出
writer := bufio.NewWriter(os.Stdout) // 创建一个Writer
// StdOut or Printer? //
if *destinationPrinter == "" {
// StdOut //
fmt.Printf("%s", resultData)
} else {
// Printer //
cmd := exec.Command("lp", "-d"+*destinationPrinter)
lpStdin, err := cmd.StdinPipe() // 连接到命令启动时标准输入的管道
if err != nil {
l.Println("Error occured when trying to send data to lp:\n", err.Error())
os.Exit(1)
}
go func() {
defer lpStdin.Close() // 在return之前关闭管道并输出相关内容
io.WriteString(lpStdin, resultData)
}()
out, err := cmd.CombinedOutput()
if err != nil {
l.Println("Error occured when sending data to lp:\n", err.Error())
os.Exit(1)
}
_, err = writer.Write(out)
if err != nil {
l.Println("Error occured when writing information to StdOut:\n", err.Error())
os.Exit(1)
}
}
根据输出方式的不同输出可分为两类:直接输出到标准输出流的和将数据传送给 lp 进行打印工作的.
测试
我的测试文件input_file是一个一行只有一个数字的txt文件,然后每一行的数字都是递增的,从1到10000.
命令输入:
$ go run selpg.go -s10 -e20 input_file
结束页为20页,那就是结束行是1440
读取标准输入,输入被重定向到input_file
$ go run selpg.go -s10 -e20 < input_file
结果同样也是输出到1440行