Go语言实现Selpg

实现:github

前言

本文介绍了使用GO语言实现Selpg这样一个程序的过程,总体参考了开发Linux命令行实用程序这篇文章,它讲的是使用C语言实现Selpg。Selpg即Select Pages,允许用户指定从输入文本(可来自文件或命令行或是另一个进程的输出)抽取的页的范围,并控制输出的位置,可以是标准输出,也可以输入至文件或子进程。

Usage

1
2
3
4
5
6
7
8
Usage: selpg [-s startPage] [-e endPage] [-l linesPerPage | -f] [-d printDest] filename

Options:
-d, --dest string (Optional) Enter printing destination
-e, --endPage int (Mandatory) Input Your endPage
-f, --pageBreak (Optional) Choosing pageBreaks mode
-l, --pageLen int (Optional) Choosing pageLen mode, enter pageLen
-s, --startPage int (Mandatory) Input Your startPage

代码结构

总共有三个函数:

  1. main函数:解析命令参数的入口函数

  2. processArgs函数:处理参数,进行错误处理

  3. processInput函数:经过processArgs函数后,这里根据命令进行(文件)操作

除此之外,还有保存参数的struct:selpgArgs,以及五个解析参数用的flag。

selpgArgs存储内容:

1
2
3
4
5
type selpgArgs struct {
startPage, endPage, pageLen, pageType int
inFilename string
printDest string
}

关键实现

  • pflag

在前面的参考文章中,可以看到如果使用C语言来解析参数,代码实现还是比较繁杂的,需要手动进行参数的获取、判断乃至解析。而在Go语言中,有很多便利的包供我们使用,在这里使用的是pflag这个包来进行参数的解析。

pflag包的使用是非常简单的,首先go get了这个包(spf13/pflag)之后,引入它就可以使用了。基本的参数绑定操作:

1
2
3
4
5
6
var ip *int = flag.Int("flagname", 1234, "help message for flagname")	//绑定"--flagname"的值到*ip上,格式为int
//或
var flagvar int //声明
func init() {
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") //绑定
}

在Selpg中,我们需要两种格式的参数,拿输入文本的起始页做例子,我们就需要同时接受--startPage-s这两种输入,pflag也允许我们这样做:

1
2
var inputS = flag.IntP("startPage", "s", -1, "...")
//startPage配合"--"使用,s配合"-"使用

最后,flag.Parse()自动进行参数的捕获、解析。如果是没有带---的参数,则可通过flag.Args()获取,这类参数的数量为flag.Narg()

  • bufio

在Selpg的实现中我使用了bufio包来进行输入输出的定向,bufio即buffered I/O,它封装了io.Reader与io.Writer,使用起来还是较为方便的。

读取输入部分:

1
2
3
4
5
6
7
8
9
10
11
12
//从命令行输入
inputReader = bufio.NewReader(os.Stdin)
//或打开一个文件后输入
file, err = os.Open(selpg.inFilename)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
inputReader = bufio.NewReader(file)
//读取输入,直到某一字符(包含此字符)
line, err = inputReader.ReadBytes('\n') //换页为行模式
line, err = inputReader.ReadBytes('\f') //换页为换页符模式

定向输出部分:

1
2
3
outputWriter = bufio.NewWriter(os.Stdout)
outputWriter.Write(line)
outputWriter.Flush() //这一步必须
  • os/exec

“-dXXX”的实现可能是比较难的一部分,这里通过os/exec包来实现,exec允许运行外部命令,它封装了os.StartProcess,故我们可以更容易地重定向输入输出,可通过管道向命令输入内容。

-d参数获得了一个打印机地址,就进行如下操作:

1
2
3
4
5
6
7
8
9
//初始化外部命令
cmd = exec.Command("lp", "-d", selpg.printDest)
//建立一个管道以输入打印内容
stdin, err = cmd.StdinPipe()
//写入管道
_, err := io.WriteString(stdin, string(line))
//关闭管道、获得输出
stdin.Close()
stderr, _ := cmd.CombinedOutput()

注意cmd.CombinedOutput()的作用是运行命令并获得组合在一起的stdout/stderr输出。

简单测试

  • selpg -s1 -e1

即,从命令行输入,并输出到命令行,打印前72行(default)

1

  • selpg -s1 -e1 -l3 input

即,从文件输入,并输出到命令行,打印前三行

2

  • selpg -s1 -e input >output 2>error

即,正确输出到文件output,错误输出至文件error(故意写成错误命令)

3

  • 测试换页符,首先建立包含换页符的文件:

4

接着,测试selpg -s1 -e3 -f input

5

  • 测试输出至打印机lp1

6

命令正确执行了,只是没有打印机

  • selpg -s1 -e20 -l20 test.txt | grep -n "Geralt"

测试读取并输出长篇小说的前20页(设置每页长20行)

Geralt

这样就实现了selpg程序。