CLI 命令行实用程序开发基础
- 开发实践
- 目的
- 要求
- 环境配置
- 代码实现
- 引用的库
- 参数结构体selpg_args
- 参数获取函数
- main函数
- 参数检查
- 输入读取
- 输出
- 文件读写
- selpg功能测试
- 单元测试
- 代码
开发实践
目的
使用 golang 开发开发 Linux 命令行实用程序中的 selpg
要求
- 请按文档使用 selpg 章节要求测试你的程序
- 请使用 pflag 替代 goflag 以满足 Unix 命令行规范, 参考:Golang之使用Flag和Pflag:
- golang 文件读写、读环境变量,请自己查 os 包
- “-dXXX” 实现,请自己查 os/exec 库,例如案例Command,管理子进程的标准输入和输出通常使用 io.Pipe,具体案例见Pipe
- 请自带测试程序,确保函数等功能正确
环境配置
- Linux go语言环境配置
- 使用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
selpg -s1 -e1 < input.txt
other_command | selpg -s10 -e20
other_command的标准输出重定向为selpg的输入写至标准输出
因为总共只有一页,而开始页为10,所以报错
selpg -s1 -e2 input.txt > output.txt
将selpg的标准输出重定向写入到 output.txt 文件
selpg -s10 -e20 input.txt 2>error.txt
将第一页和第二页写至标准输出,所有错误信息重定向写到error.txt文件
selpg -s1 -e2 input.txt >output.txt 2>error.txt
将第一页和第二页重定向输出到 output.txt,将错误信息重定向写到 error.txt
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),但是因为错误信息被写到空设备(被丢弃),所以看不见错误信息
selpg -s1 -e2 input.txt | lp
selpg -s1 -e2 input.txt | wc
将selpg的输出重定向作为lp/wc的输入
selpg -s1 -e2 -l25 input.txt
设置页长为25
selpg -s1 -e2 -f input.txt
页长由换页符决定,所以会输出全部内容
selpg -s1 -e1 -dlp1 input.txt
将第一页由管道输送至命令“lp -dlp1”,该命令将使输出在打印机 lp1 上打印,但由于没有打印机,所以会报错
selpg -s10 -e20 input_file > output_file 2>error_file &
该命令输入后,显示出进程pid,并马上出现shell提示符,selpg进程在后台运行,用户可以做其他工作
单元测试
将开发 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)
}
}
测试通过
代码