CLI 命令行实用程序开发基础

  • 开发实践
  • 目的
  • 要求
  • 环境配置
  • 代码实现
  • 引用的库
  • 参数结构体selpg_args
  • 参数获取函数
  • main函数
  • 参数检查
  • 输入读取
  • 输出
  • 文件读写
  • selpg功能测试
  • 单元测试
  • 代码


开发实践

目的

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

要求

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

环境配置

  1. Linux go语言环境配置
  2. 使用go get github.com/spf13/pflag手动安装pflag库
go get github.com/spf13/pflag

代码实现

引用的库

import  (
	flag "github.com/spf13/pflag"
    "fmt"
    "os"
    "io"
    "bufio"
    "os/exec"
)

参数结构体selpg_args

type selpg_args struct {
        start_page int 
        end_page int
        page_len int
        page_type bool
        in_filename string
        print_dest string
}

参数获取函数

func getArgs(args *selpgArgs) {
    pflag.IntVarP(&(args.startPage), "startPage", "s", -1, "Define startPage")
    pflag.IntVarP(&(args.endPage), "endPage", "e", -1, "Define endPage")
    pflag.IntVarP(&(args.pageLen), "pageLength", "l", 20, "Define pageLength")
    pflag.StringVarP(&(args.printDest), "printDest", "d", "", "Define printDest")
    pflag.BoolVarP(&(args.pageType), "pageType", "f", false, "Define pageType")
    pflag.Parse()
 
    argLeft := pflag.Args()
    if len(argLeft) > 0 {
        args.inFileName = string(argLeft[0])
    } else {
        args.inFileName = ""
    }
}

main函数

func main() {
    sa := selpg_args{-1,-1,72,false,"",""}
    get_args(&sa)
    check_args(&sa)
    process_input(&sa)

}

参数检查

检查输入的参数:参数个数,开始页,结束页,每页行数越界,开始页数>结束页数

func check_args(sa *selpg_args) {
    if len(os.Args) < 3 {
        fmt.Fprintf(os.Stderr, "%s: not enough arguments\n", progname)
        usage()
        os.Exit(1)
    }
    if sa.start_page < 1 || sa.start_page > INT_MAX {
        fmt.Fprintf(os.Stderr, "%s: invalid start page %d\n", progname, sa.start_page)
        usage()
        os.Exit(2)
    }
    if sa.end_page < 1 || sa.end_page > INT_MAX {
        fmt.Fprintf(os.Stderr, "%s: invalid end page %d\n", progname, sa.end_page)
        usage()
        os.Exit(3)
	}
	if page_type == true && (sa.page_len < 1 || sa.page_len > INT_MAX) {
        fmt.Fprintf(os.Stderr, "%s: invalid page length %d\n", progname, sa.page_len)
        usage()
        os.Exit(4)
    }
    if sa.end_page < sa.start_page {
        fmt.Fprintf(os.Stderr, "%s: end page should not be less than start pag\n", progname)
        usage()
        os.Exit(3)
    }
    if sa.in_filename != "" {
        if _, err := os.Stat(sa.in_filename); os.IsNotExist(err) {
            fmt.Fprintf(os.Stderr, "%s: input file \"%s\" does not exist\n", progname, sa.in_filename)
			os.Exit(5);
        }
    }
}

输入读取

如果没有输入文件参数那么输入来自标准输入,否则就重定向文件输入

var reader *bufio.Reader
if sa.in_filename == "" {
    reader = bufio.NewReader(os.Stdin)
} else {
    fin, err := os.Open(sa.in_filename)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%s: could not open input file \"%s\"\n", progname, sa.in_filename)
        os.Exit(6)
    }
    reader = bufio.NewReader(fin)
    defer fin.Close()
}

输出

通过exec.Command创建了一个子进程,执行打印,打印的目的地为输入命令中的目的地,如果没有目的地参数,那么就用标准输出,然后将程序的输出流writer设为打印进程的输入管道,实现将读取的内容通过管道写到目的文件中

var writer io.WriteCloser 
if sa.print_dest == "" {
    writer = os.Stdout
} else {
    cmd := exec.Command("lp","-d"+ sa.print_dest)
    var err error
    if writer, err = cmd.StdinPipe(); err != nil {
        fmt.Fprintf(os.Stderr, "%s: could not open pipe to \"%s\"\n", progname, sa.print_dest)
        fmt.Println(err)
        os.Exit(7)
    }
    cmd.Stdout = os.Stdout;
    cmd.Stderr = os.Stderr;
    if err = cmd.Start(); err != nil {
        fmt.Fprintf(os.Stderr, "%s: cmd start error\n", progname)
        fmt.Println(err)
        os.Exit(8)
    }
}

文件读写

采用两种基本思路(定长和不定长),在不定长的读写中,当读入到 \f 页结束符时,页增加一

line_ctr, page_ctr, page_len := 1, 1, sa.page_len
ptFlag := '\n'
if sa.page_type {
    ptFlag = '\f'
    page_len = 1
}

for {
    line, crc := reader.ReadString(byte(ptFlag));
    if crc != nil && len(line) == 0 {
        break
    }
    if line_ctr > page_len {
        page_ctr++
        line_ctr = 1
    }
    if page_ctr >= sa.start_page && page_ctr <= sa.end_page {
        _, err := writer.Write([]byte(line))
        if err != nil {
            fmt.Println(err)
            os.Exit(9)
        }
    }
    line_ctr++
}

if page_ctr < sa.start_page {
    fmt.Fprintf(os.Stderr, "\n%s: start_page (%d) greater than total pages (%d)," + " no output written\n", progname, sa.start_page, page_ctr)
} else if page_ctr < sa.end_page {
    fmt.Fprintf(os.Stderr, "\n%s: end_page (%d) greater than total pages (%d)," + " less output than expected\n", progname, sa.end_page, page_ctr)
}

selpg功能测试

使用go install命令安装selpg命令

go install github.com/user/selpg

selpg -s1 -e1 input.txt

python飞控 飞控cli命令教程_python飞控

selpg -s1 -e1 < input.txt

python飞控 飞控cli命令教程_bash_02


other_command | selpg -s10 -e20

other_command的标准输出重定向为selpg的输入写至标准输出

因为总共只有一页,而开始页为10,所以报错

python飞控 飞控cli命令教程_i++_03

selpg -s1 -e2 input.txt > output.txt

将selpg的标准输出重定向写入到 output.txt 文件

python飞控 飞控cli命令教程_重定向_04


python飞控 飞控cli命令教程_i++_05


selpg -s10 -e20 input.txt 2>error.txt

将第一页和第二页写至标准输出,所有错误信息重定向写到error.txt文件

python飞控 飞控cli命令教程_bash_06


selpg -s1 -e2 input.txt >output.txt 2>error.txt

将第一页和第二页重定向输出到 output.txt,将错误信息重定向写到 error.txt

python飞控 飞控cli命令教程_i++_07


python飞控 飞控cli命令教程_i++_08


selpg -s10 -e20 input.txt >output.txt 2>/dev/null

将第10页到第20页重定向输出到 output.txt,错误信息被重定向写到空设备(/dev/null),即被丢弃

因为总页数为2,但是读取文件从第10页开始,所以会显示错误信息 selpg: start_page (10) greater than total pages (2),但是因为错误信息被写到空设备(被丢弃),所以看不见错误信息

python飞控 飞控cli命令教程_i++_09

selpg -s1 -e2 input.txt | lp
selpg -s1 -e2 input.txt | wc
将selpg的输出重定向作为lp/wc的输入

python飞控 飞控cli命令教程_i++_10

selpg -s1 -e2 -l25 input.txt
设置页长为25

python飞控 飞控cli命令教程_重定向_11

selpg -s1 -e2 -f input.txt
页长由换页符决定,所以会输出全部内容

python飞控 飞控cli命令教程_i++_12


selpg -s1 -e1 -dlp1 input.txt

将第一页由管道输送至命令“lp -dlp1”,该命令将使输出在打印机 lp1 上打印,但由于没有打印机,所以会报错

python飞控 飞控cli命令教程_i++_13


selpg -s10 -e20 input_file > output_file 2>error_file &

该命令输入后,显示出进程pid,并马上出现shell提示符,selpg进程在后台运行,用户可以做其他工作

python飞控 飞控cli命令教程_bash_14

单元测试

开发 Linux 命令行实用程序中的 selpg.c文件编译成 a.out 文件,对比两者对于之前的命令的输出

package selpg

import (
    "testing"
	"os/exec"
)

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

    stdout, err = exec.Command("bash", "-c", "selpg -s1 -e1 <input.txt").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s1 -e1 <input.txt").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "ls | selpg -s10 -e20").Output()
    stdout2, err2 = exec.Command("bash", "-c", "ls | ./a.out -s10 -e20").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "selpg -s1 -e2 input.txt >output.txt").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s1 -e2 input.txt >output.txt").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "selpg -s10 -e20 input.txt 2>error.txt").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s10 -e20 input.txt 2>error.txt").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "selpg -s1 -e2 input.txt >output.txt 2>error.txt").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s1 -e2 input.txt >output.txt 2>error.txt").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "selpg -s10 -e20 input.txt >output.txt 2>/dev/null").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s10 -e20 input.txt >output.txt 2>/dev/null").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "selpg -s1 -e2 input.txt 2>error.txt | wc").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s1 -e2 input.txt 2>error.txt | wc").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "selpg -s1 -e2 -l25 input.txt").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s1 -e2 -l25 input.txt").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "selpg -s1 -e2 -f input.txt").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s1 -e2 -f input.txt").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "selpg -s1 -e1 -dlp1 input.txt").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s1 -e1 -dlp1 input.txt").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }

    stdout, err = exec.Command("bash", "-c", "selpg -s10 -e20 input.txt >output.txt 2>error.txt &").Output()
    stdout2, err2 = exec.Command("bash", "-c", "./a.out -s10 -e20 input.txt >output.txt 2>error.txt &").Output()
    for i:=0;i<len(stdout);i++{
		if stdout[i] != stdout2[i]{
			t.Error("error")
			break
		}
	}
	if err != nil {
        t.Error(err)
    }
}

测试通过

python飞控 飞控cli命令教程_i++_15

代码