CLI(Command Line Interface)实用程序是Linux下应用开发的基础。在开发领域,CLI在编程、调试、运维、管理中提供了图形化程序不可替代的灵活性与效率。这次的主要任务是使用golang开发Linux命令行实用程序中的selpg。
由于老师要求使用 pflag 替代 goflag 以满足 Unix 命令行规范,所以我们需要安装和导入pflag,这一步实现可以在vsc的终端中输入如下命令:
go get -u "github.com/spf13/pflag"
然后我们需要在程序开头引入/需要的包:
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"github.com/spf13/pflag"
)
接下来定义保存参数数据的结构体:
type selpgArgs struct {
startPage int
endPage int
inFileName string
pageLen int
pageType bool
printDest string
}
使用 pflag 替代 goflag 以满足 Unix 命令行规范:
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", 72, "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 = ""
}
}
接下来用一个函数判断输入的命令的参数的正确性,如果有误,则指出错误并终止程序,如果无误则输出各参数:
func checkArgs(args *selpgArgs) {
if (args.startPage == -1) || (args.endPage == -1) {
fmt.Fprintf(os.Stderr, "\n[Error]起始页码和结尾页码不能为空\n")
os.Exit(2)
} else if (args.startPage <= 0) || (args.endPage <= 0) {
fmt.Fprintf(os.Stderr, "\n[Error]起始页码和结尾页码不能为负数\n")
os.Exit(3)
} else if args.startPage > args.endPage {
fmt.Fprintf(os.Stderr, "\n[Error]起始页码不能大于结尾页码\n")
os.Exit(4)
} else if (args.pageType == true) && (args.pageLen != 72) {
fmt.Fprintf(os.Stderr, "\n[Error]不能同时使用-l -f\n")
os.Exit(5)
} else if args.pageLen <= 0 {
fmt.Fprintf(os.Stderr, "\n[Error]页数不能小于1\n")
os.Exit(6)
} else {
pageType := "page length."
if args.pageType == true {
pageType = "The end sign /f."
}
fmt.Printf("\n")
fmt.Printf("起始页: %d\n末页: %d\n输入文件: %s\n页数: %d\n书页类型: %s\n输出文件: %s\n", args.startPage, args.endPage, args.inFileName, args.pageLen, pageType, args.printDest)
}
}
当命令各参数无误时,就开始调用excute函数执行命令。该部分函数先检查输入,然后打开文件,检查是否出现错误,最后判断是否有-d参数。代码如下所示:
func excute(args *selpgArgs) {
var fin *os.File
if args.inFileName == "" {
fin = os.Stdin
} else {
checkFileAccess(args.inFileName)
var err error
fin, err = os.Open(args.inFileName)
checkError(err, "File input")
}
output(args.printDest, fin, args.startPage, args.endPage, args.pageLen, args.pageType)
}
func checkFileAccess(filename string) {
_, errFileExits := os.Stat(filename)
if os.IsNotExist(errFileExits) {
fmt.Fprintf(os.Stderr, "\n[Error]: 输入文件 \"%s\" 不存在\n", filename)
os.Exit(7)
}
}
输出函数output将输入的文件,按页码要求读取并输出到fout中,此函数中也会判断分页方式:
func output(printDest string, fin *os.File, pageStart int, pageEnd int, pageLen int, pageType bool) {
lineCount := 0
pageCount := 1
buf := bufio.NewReader(fin)
var cmd *exec.Cmd
var fout io.WriteCloser
if len(printDest) > 0 {
cmd, fout = cmdExec(printDest)
}
for true {
var line string
var err error
if pageType {
line, err = buf.ReadString('\f')
pageCount++
} else {
line, err = buf.ReadString('\n')
lineCount++
if lineCount > pageLen {
pageCount++
lineCount = 1
}
}
if err == io.EOF {
break
}
checkError(err, "file read in\n")
if (pageCount >= pageStart) && (pageCount <= pageEnd) {
var outputErr error
if len(printDest) == 0 {
_, outputErr = fmt.Fprintf(os.Stdout, "%s", line)
} else {
_, outputErr = fout.Write([]byte(line))
checkError(outputErr, "pipe input")
}
checkError(outputErr, "Error happend when output the pages.")
}
}
if len(printDest) > 0 {
fout.Close()
errStart := cmd.Run()
checkError(errStart, "CMD Run")
}
if pageCount < pageStart {
fmt.Fprintf(os.Stderr, "\n[Error]: 起始页 (%d) 大于总页数 (%d), 无输出\n", pageStart, pageCount)
os.Exit(9)
} else if pageCount < pageEnd {
fmt.Fprintf(os.Stderr, "\n[Error]: 末页 (%d) 大于总页数 (%d), 输出不全\n", pageEnd, pageCount)
os.Exit(10)
}
}
在src文件夹下创建一个新的文件夹,命名为selpg,然后在文件夹里面放入go文件,使用命令
go install selpg
生成bin/selpg文件,然后在bin文件夹中放入测试文件,控制台上进入bin,就可以测试命令了。