CLI命令行实用程序开发
1. 实验准备
1. CLI概述
CLI(Command Line Interface)实用程序是Linux下应用开发的基础。正确的编写命令行程序让应用与操作系统融为一体,通过shell或script使得应用获得最大的灵活性与开发效率。例如:
Linux提供了cat、ls、copy等命令与操作系统交互;
go语言提供一组实用程序完成从编码、编译、库管理、产品发布全过程支持;
容器服务如docker、k8s提供了大量实用程序支撑云服务的开发、部署、监控、访问等管理任务;
git、npm等也是大家比较熟悉的工具。
尽管操作系统与应用系统服务可视化、图形化,但在开发领域,CLI在编程、调试、运维、管理中提供了图形化程序不可替代的灵活性与效率。
2. 基础知识
本次作业提供了Linux命令行程序的C语言文档:
3. os库的使用
本次作业将会用到的os中一些函数:
os.Exit(int)
: 用于程序退出,不同的参数代表不同的退出情况
os.Stdin
: 标准输入,selpg默认使用Stdin
os.Stdout
: 标准输出,selpg默认使用Stdout
os.Open(string)
: 用于打开只读文件,返回值是文件指针和错误信息
os.OpenFile(string)
: 高级版的打开文件,可以设置打开文件的方式,文件的操作形式。返回值是文件指针和错误信息。
os.Close(string)
: 用于关闭文件
os.O_WRONLY
: 只读文件标识符
os.ModeAppend
: Append模式
4. pflag库学习
1. 首先要让go获取pflag,终端运行命令:go get github.com/spf13/pflag
2. 在程序中引用这个包:import flag "github.com/spf13/pflag"
3. 使用pflag库
(1) 对flag进行绑定。
当你输入命令的时候,flag就会自动识别命令并为你绑定好的变量进行赋值。
以第一条命令为例:
第一个参数就是需要绑定的指针
第二个参数是命令中的对应的表示,比如上面的第一行代码,就会在输入–s 123
的时候,将123这个值赋值给sa.startPage。
第三个参数是s的初始值,若是你没有对s进行赋值,那么s就会维持这个初始值。
第四个参数是命令提示,当你使用–h
查询命令含义的时候,这串字符就会对应的出现在每个命令参数后面。
(2) flag.Parse()
用于对命令行参数进行解析。这个过程就是刚刚所说的赋值过程,比如你输入以下命令:
selpg -s1 -e2 -l66
然后执行了 flag.Parse() ,那么flag就会自动将1赋值给s,2赋值给e,66赋值给l。
(3) flag.PrintDefaults()
用于输出帮助信息,就如前面提到的,要是你不知道各命令行参数是什么意思,调用这个函数就会将刚刚绑定flag的第四个参数中的字符床显示在屏幕中。
(4) flag.NArg()
返回没有flag与之对应的参数的数量
(5) flag.Arg(int)
访问指定位置的non-flag参数,即没有flag与之对应的参数
2. 程序设计
在$GOPATH/src
下分别创建selpg
和lp1
文件夹,然后在selpg下创建selpg.go
,lp1下创建lp1.go
1. selpg.go
文件
导入所需包
import(
"bufio"
"fmt"
"io"
"os"
"os/exec"
"github.com/spf13/pflag"
)
定义结构体
type selpg_args struct{
startPage int //开始页
endPage int //结束页
input string //输入文件
pageLen int //页长(页行数)
pageType bool //两种类型,true表示-f,false表示-lnumber
output string // 输出文件
}
main函数
func main(){
var sa selpg_args
ArgsInit(&sa)
Input(&sa)
}
ArgsInit函数
func ArgsInit(sa *selpg_args){
pflag.IntVarP(&(sa.startPage),"startPage","s",-1,"start page")
pflag.IntVarP(&(sa.endPage),"endPage","e",-1,"end page")
pflag.IntVarP(&(sa.pageLen),"pageLen","l",72,"the length of page")
pflag.BoolVarP(&(sa.pageType),"pageType","f",false,"page type")
pflag.StringVarP(&(sa.output),"output","d","","print destination")
pflag.Parse()
sa_left:=pflag.Args() // 其余参数
if len(sa_left) > 0 {
sa.input=sa_left[0]
} else {
sa.input=""
}
check(sa)
}
check函数
func check(sa *selpg_args){
if sa==nil{
fmt.Fprintf(os.Stderr,"\n[Error]The args is nil! Please check your program!\n\n")
os.Exit(0)
}else if (sa.startPage==-1)||(sa.endPage==-1){
fmt.Fprintf(os.Stderr,"\n[Error]The startPage and endPage do not allowed empty! Please check your command.\n\n")
os.Exit(0)
}else if sa.startPage>sa.endPage{
fmt.Fprintf(os.Stderr,"\n[Error]The startPage can not be bigger than the endPage! Please check your command!\n\n")
os.Exit(0)
}else if (sa.startPage<1)||(sa.endPage<1){
fmt.Fprintf(os.Stderr,"\n[Error]The startPage and endPage do not exist! Please check your command.\n\n")
os.Exit(0)
}
if sa.pageType==false&&sa.pageLen<1 {
fmt.Fprintf(os.Stderr,"\n[Error]You should input valid page length!\n\n")
os.Exit(0)
}
}
Input函数
func Input(sa *selpg_args){
var reader *bufio.Reader
var cmd *exec.Cmd=nil
var stdin io.WriteCloser=nil
writer:=bufio.NewWriter(os.Stdout)
if sa.input=="" {
reader=bufio.NewReader(os.Stdin)
} else{
fileIn,err:=os.Open(sa.input)
defer fileIn.Close()
if err!=nil {
os.Stderr.Write([]byte("Open file error.\n"))
os.Exit(0)
}
reader=bufio.NewReader(fileIn)
}
if sa.output!="" {
cmd=exec.Command(sa.output)
var pipeErr error;
stdin,pipeErr=cmd.StdinPipe()
if pipeErr!=nil {
fmt.Println(pipeErr)
os.Exit(0)
}
startErr:=cmd.Start()
if startErr!=nil {
fmt.Println(startErr)
os.Exit(0)
}
}
lineCtr:=0
pageCtr:=1
endSign:='\n'
if sa.pageType==true {
endSign='\f'
}
for{
strLine,errRead:=reader.ReadBytes(byte(endSign))
if errRead!=nil {
if errRead==io.EOF {
writer.Flush()
break
} else{
os.Stderr.Write([]byte("Read bytes from reader failed.\n"))
os.Exit(0)
}
}
if pageCtr>=sa.startPage&&pageCtr<=sa.endPage {
_,errWrite:=writer.Write(strLine)
if errWrite!=nil {
fmt.Println(errWrite)
os.Stderr.Write([]byte("Write bytes to out failed.\n"))
os.Exit(0)
}
if stdin!=nil {
_,errWrite:=stdin.Write(strLine)
if errWrite!=nil {
fmt.Println(errWrite)
os.Stderr.Write([]byte("Write bytes to out failed.\n"))
os.Exit(0)
}
}
}
if sa.pageType==true {
pageCtr++
} else{
lineCtr++
}
if sa.pageType==false&&lineCtr==sa.pageLen {
lineCtr=0
pageCtr++
}
if pageCtr>sa.endPage {
writer.Flush()
break
}
}
if stdin!=nil {
stdin.Close()
}
if cmd!=nil {
if err:=cmd.Wait();err!=nil {
fmt.Println(err)
os.Exit(0)
}
}
}
2. lp1.go
文件
package main
import(
"bufio"
"io"
"os"
)
func main(){
reader:=bufio.NewReader(os.Stdin)
writer:=bufio.NewWriter(file)
file,openErr:=os.OpenFile("./lp1.txt",os.O_RDWR|os.O_CREATE|os.O_APPEND,0644)
if openErr!=nil {
panic(openErr)
}
for{
line,errRead:=reader.ReadBytes('\n')
if errRead!=nil {
if errRead==io.EOF {
break
} else {
os.Stderr.Write([]byte("Fail to read bytes from reader.\n"))
os.Exit(0)
}
}
_,errWrite:=writer.Write(line)
if errWrite!=nil {
os.Stderr.Write([]byte("Fail to write bytes to file.\n"))
os.Exit(0)
}
writer.Flush()
}
}
3. input.txt
,output.txt
和error.txt
文件(自己准备)
3. 功能测试
在$GOPATH/src/selpg下终端执行以下命令:
go build selpg.go
go install selpg
在$GOPATH/src/lp1下终端执行以下命令:
go build lp1.go
go install lp1
1. $ selpg -s1 -e1 input_file
2. $ selpg -s1 -e1 < input_file
3. other_command | selpg -s10 -e20
4. $ selpg -s10 -e20 input_file >output_file
5. $ selpg -s10 -e20 input_file 2>error_file
6. $ selpg -s10 -e20 input_file >output_file 2>error_file
7. $ selpg -s10 -e20 input_file >output_file 2>/dev/null
8. $ selpg -s10 -e20 input_file >/dev/null
9. $ selpg -s10 -e20 input_file | other_command