服务计算 | 开发简单 CLI 程序

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

1、概述

  CLI(Command Line Interface)实用程序是Linux下应用开发的基础。正确的编写命令行程序让应用与操作系统融为一体,通过shell或script使得应用获得最大的灵活性与开发效率。Linux提供了cat、ls、copy等命令与操作系统交互;go语言提供一组实用程序完成从编码、编译、库管理、产品发布全过程支持;容器服务如docker、k8s提供了大量实用程序支撑云服务的开发、部署、监控、访问等管理任务;git、npm等都是大家比较熟悉的工具。尽管操作系统与应用系统服务可视化、图形化,但在开发领域,CLI在编程、调试、运维、管理中提供了图形化程序不可替代的灵活性与效率。

2、基础知识

  几乎所有语言都提供了完善的 CLI 实用程序支持工具。以下是一些入门文档(c 语言):

  • 开发 Linux 命令行实用程序。
  • Linux命令行程序设计

如果你熟悉 python :

  • Using Python to create UNIX command line tools

阅读以后你应该知道 POSIX/GNU 命令行接口的一些概念与规范。命令行程序主要涉及内容:

  • 命令
  • 命令行参数
  • 选项:长格式、短格式
  • IO:stdin、stdout、stderr、管道、重定向
  • 环境变量

3、Golang 的支持

使用os,flag包,最简单处理参数的代码:

package main

import (
    "fmt"
    "os"
)

func main() {
    for i, a := range os.Args[1:] {
        fmt.Printf("Argument %d is %s\n", i+1, a)
    }

}

使用flag包的代码:

package main

import (
    "flag" 
    "fmt"
)

func main() {
    var port int
    flag.IntVar(&port, "p", 8000, "specify port to use.  defaults to 8000.")
    flag.Parse()

    fmt.Printf("port = %d\n", port)
    fmt.Printf("other args: %+v\n", flag.Args())
}

中文参考:

  • 标准库—命令行参数解析FLAG
  • Go学习笔记:flag库的使用

更多代码实践:

  • cat demo
  • goimports 实现

开发简单 CLI 程序 

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

selpg命令格式:

-s:startPage,后面接开始读取的页号
-e:endPage,后面接结束读取的页号
-l:后面跟行数,代表多少行分为一页
-f:该标志无参数,代表按照分页符’\f’ 分页,一般默认72行为一页
-d:“-dDestination”选项将选定的页直接发送至打印机,“Destination”应该是 lp 命令“-d”选项可接受的打印目的地名称
input_file,output_file 2,error_file:输入文件、输出文件、错误信息文件的名字

将项目分为两部分:

  • 读取命令行的命令,并解析该命令
  • 根据命令打开相应文件,并对相应文件进行文件操作

1.代码设计

1.引入所需包

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

这里采用的是pflag解析命令行参数,若要import pflag包,要先在本地安装spf13/pflag:

go get github.com/spf13/pflag

pflag基本的使用和“flag包”基本相同

新增:

  • 添加shorthand参数
// func IntP(name, shorthand string, value int, usage string) *int
// IntP is like Int, but accepts a shorthand letter that can be used after a single dash.
var ip= flag.IntP("flagname", "f", 1234, "help message")
  • 设置非必须选项的默认值
var ip = flag.IntP("flagname", "f", 1234, "help message")
flag.Lookup("flagname").NoOptDefVal = "4321"

2.创建命令行参数结构体:包括开始页面、结束页面、操作文件名、操作页面长度等数据

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

3.main函数

func main() {
	args := new(selpg_args)
	getArgs(args)
	check(args)
	process(args)
}

4.getArgs()函数:命令处理函数,得到相应参数

func getArgs(args *selpg_args) {
    //提示信息,如果给出的参数不正确或者需要查看帮助 -help,那么会给出这里指定的字符串
    pflag.Usage = usage
    pflag.IntVarP(&(args.startPage), "start", "s", 0, "start page")
    pflag.IntVarP(&(args.endPage), "end", "e", 0, "end page")
    pflag.IntVarP(&(args.pageLen), "line", "l", 72, "page len")
    pflag.StringVarP(&(args.printDestination), "destionation", "d", "", "print destionation")
    pflag.BoolVarP(&(args.pageType), "type", "f", false, "type of print")
    pflag.Parse()
    //从第一个不能解析的参数开始,后面的所有参数都是无法解析的。即使后面的参数中含有预定义的参数
    //其他参数
    othersArg := pflag.Args()
    if len(othersArg) > 0 {
        args.inFile = othersArg[0]
    } else {
        args.inFile = ""
    }
}

5.check()函数:进行简单的命令的逻辑错误检查,比如说开始、结束页面为0或负数,开始页面在结束页面之后等

func check(args *selpg_args) {
	if args.startPage == -1 || args.endPage == -1 {
		os.Stderr.Write([]byte("you should input -s -e at least\n"))
		os.Exit(0)
	}
	if args.startPage < 1 {
		os.Stderr.Write([]byte("invalid start page\n"))
		os.Exit(1)
	}
	if args.endPage < 1 || args.endPage < args.startPage {
		os.Stderr.Write([]byte("invalid end page\n"))
		os.Exit(2)
	}
	if args.pageLen < 1 {
		os.Stderr.Write([]byte("invalid page length\n"))
		os.Exit(3)
	}
}

6.process()函数:根据命令打开相应文件,并对相应文件进行文件操作

func process(args *selpg_args) {
    ......
    
    if args.inFile != "" {
        fin, err1 = os.Open(args.inFile)
        if err1 != nil {
            fmt.Fprintf(os.Stderr, "Can not open input file \"%s\"\n",  args.inFile)
            os.Exit(11)
        }
    }

    ......

    rd := bufio.NewReader(fin)
    writer := bufio.NewWriter(os.Stdout)
    if args.pageType == true {
        process_f(rd, writer, args, &page_ctr)
    } else {
        process_l(rd, writer, args, &page_ctr, &line_ctr)
    }

    ......

    fin.Close()
    fout.Close()
}

process()子函数,这里以process_l()函数为例:

func process_l(reader *bufio.Reader, writer *bufio.Writer, args *selpg_args, pageCtr *int, lineCtr *int) {
    *lineCtr = 0
    *pageCtr = 1
    for {
        line, err := reader.ReadBytes('\n')
        if err != nil {
            //遇到任何错误立即返回,并忽略 EOF 错误信息
            if err == io.EOF {
                break
            }
            os.Stderr.Write([]byte("read bytes from Reader error\n"))
            os.Exit(5)
        }
        *lineCtr++
        if *lineCtr > args.pageLen{
            *lineCtr = 0
            *pageCtr++
        }
        if *pageCtr >= args.startPage && *pageCtr <= args.endPage {
            _, errW := writer.Write(line)
            if errW != nil {
                os.Stderr.Write([]byte("Write to file fail\n"))
                os.Exit(6)
            }
            writer.Flush()
        }
    }
}

2.运行测试

说明:test.txt为测试文档

selpg -s1 -e1 test.txt(默认显示72行)

服务计算 | 开发简单 CLI 程序_第1张图片

selpg -s1 -e1 -l12 test.txt

服务计算 | 开发简单 CLI 程序_第2张图片

selpg -s1 -e1 < test.txt

服务计算 | 开发简单 CLI 程序_第3张图片

selpg -s1 -e1 -l12 test.txt >out.txt

服务计算 | 开发简单 CLI 程序_第4张图片

selpg -s1 -e1 -l12 test.txt>error.txt

(这里无错误输出)

服务计算 | 开发简单 CLI 程序_第5张图片

selpg -s1 -e1 -l12 test.txt >/dev/null

selpg -s1 -e0 -l12 test.txt >/dev/null

服务计算 | 开发简单 CLI 程序_第6张图片

ps | selpg -s1 -e1 -l12 test.txt

服务计算 | 开发简单 CLI 程序_第7张图片

selpg -s1 -e1 -l12 test.tx | cat -n

服务计算 | 开发简单 CLI 程序_第8张图片

你可能感兴趣的:(服务计算 | 开发简单 CLI 程序)