GO语言实现selpg

Go语言实现selpg


程序基本说明

本文演示如何用Go语言编写与 cat、ls、pr 和 mv 等标准命令类似的 Linux 命令行实用程序。我选择了一个名为 selpg 的实用程序,这个名称代表 SELect PaGes。selpg 允许用户指定从输入文本抽取的页的范围,这些输入文本可以来自文件/标准输入/另一个进程。

该程序是Go语言版本,具体程序说明可参考本程序的C语言版本,链接:selpg程序说明


代码地址

代码链接


程序功能模块

0.main函数

func main() {
    args := new(selpg_args)
    receive_args(args)
    check_args(args)
    process_input(args)
}

1.func receive_args(args *selpg_args)

解析命令行参数,此处可利用flag包,或者自己解析,本文利用flag包。

2.func check_args(args *selpg_args)

检测命令行参数是否有错误,如结束页码小于开始页码等。

3.func process_input(args *selpg_args)

根据参数,执行相应逻辑(处理输入,输出)


各功能模块的具体实现

1.构建命令行参数的结构体

各模块都要用到命令行参数,为了方便,我们先构建一个命令行参数的结构体如下:

type selpg_args struct {
    startPage        int
    endPage          int
    inFile           string
    pageLen          int
    pageType         bool //ture for -f, false for -l
    printDestination string
}

2. func receive_args(args *selpg_args)

我是用flag包解析命令行参数的,所以键入命令时输入参数应为 -s=1 或-s 1的形式,与原本的要求不太一致,不过也算差强人意吧。
引用flag包,import “flag”
为了获取参数,需要将flag绑定到各个变量上,如下

flag.IntVar(&(args.startPage), "s", -1, "start page")

这个代码的意思就是程序会检测命令行输入的参数,如果有形如-s=3或-s 3的输入,那么args.startPage的值就会被赋为3。如果没有此类输入,则为默认值-1。
这里需要注意的是-f这个选项,因为flag一般要求选项后必须带参数,为了解决这个问题,可以这样

    flag.BoolVar(&(args.pageType), "f", false, "type of print")

如果是Bool类型,则可以不带参数。这里的做法是如果有-f,则pageType为true,表示按行读取。如果没有-f或输入为-l=2,则pageType为false,表示以’\f’为页结束符
注意绑定完所有变量之后,要调用flag.Parse()
代码如下:

if len(othersArg) > 0 {
    args.inFile = othersArg[0]
} else {
    args.inFile = ""
}

3.func check_args(args *selpg_args)

这里没有什么难度,主要就是实现判断参数是否满足要求。需要注意的是,输出错误信息时要使用类似如下的函数:

os.Stderr.Write([]byte("invalid end page\n"))
os.Exit(0)

这样错误信息才会被导入到Stderr中,从而可以在命令行中输入如./selpg -s=1 -e=1 2>errorFile.txt,将错误信息导入文件中。

4.func process_input(args *selpg_args)

函数主要逻辑:

  1. 判断args.inFile是否为空,为空->输入为标准输入;否则,标准输入为文件流
  2. 判断是否为-d类型,是则跳到步骤5;否则跳到步骤3
  3. 分为-L和-f两种类型,读取输入,再输出到标准输出
  4. 启动子进程,进程名为args.printDestionation,建立管道,子进程的标准输入是selpg进程的标准输出。分为-L和-f两种类型,读取输入,再输出到标准输出。关闭管道和子进程。
  5. 判断文件总页数是否大于开始页码和结束页码

该函数利用到的包解析

1.import “bufio”

官网解释:bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
现在还不是特别了解,只能说说自己的理解,术语不太精确,理解也不一定正确。欢迎各位留言指正哈~

关于bufio.Reader:

os.Stdlin和打开文件都能得到输入数据流,而bufio.Reader就是一个对象,该对象带有一个缓冲区,与一个数据流绑定。通过

fileIn, err := os.Open(args.inFile)
defer fileIn.Close()
if err != nil {
    os.Stderr.Write([]byte("open file error\n"))
    os.Exit(4)
}
reader = bufio.NewReader(fileIn)

我们创建了一个reader,是bufio.Reader类型,它绑定了一个文件流,用bufio.Reader对象的好处是我们可以利用这个对象的函数方法,如

line, err := reader.ReadBytes('\n')

这样就能按行读取缓冲区中的数据。

关于bufio.Writer:
这个对象和bufio.Reader很像,不过它是绑定了一个输出数据流。
可以利用

writer := bufio.NewWriter(os.Stdout)
errW := writer.Write(byte[]("<(* ̄▽ ̄*)/"))

导出到标准输出
同样也可以绑定到文件数据流中

2.import “os.exec”

官方解释:exec包执行外部命令。它包装了os.StartProcess函数以便更容易的修正输入和输出,使用管道连接I/O,以及作其它的一些调整。
我的理解是,这个包可以帮助我们在程序中启动子进程并使用管道连接I/0。具体用法是先建立一个子进程的输入管道,启动子进程,从父进程接收标准输出,关闭管道,关闭子进程。
几个重要函数:
exec.Command(filePath):

cmd_grep := exec.Command("./" + args.printDestination)

创建一个命令对象,参数为子进程路径和子进程参数(可选)

func (c *Cmd) StdinPipe() (io.WriteCloser, error):
!!!这个函数很重要,它构建了到子进程的输入管道,返回一个io.WriterClose对象,这个对象绑定了一个数据流,该数据流是子进程的数据输入流(我的理解而已,也不确定对不对)。所以可以直接调用对象的方法writer.Write([]byte)将数据转化为到子进程的标准输入,如:

_, errW := writer.Write([]byte("ε=ε=ε=(#>д<)ノ"))

func (c *Cmd) Start() error:
启动子进程

func (*Cmd) Wait:
在命令执行完成后调用,返回所调用命令的执行情况,同时释放资源

具体实现

具体实现不是太难,只要用上面提到的几个包,就能完成任务。如果要看源代码,可以到我的Github上下载。

参考博客、文档

GO官方文档_本文提到的包文档里面都有
GO切片:用法和本质
博客:os.exec的用法
博客:flag解析命令行参数
博客:Go文件读写4种方法
博客:理解GO包的导入

猜你喜欢

转载自blog.csdn.net/kunailin/article/details/78262456