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

#1、概述
主要内容为使用 golang 开发 Linux 命令行实用程序 中的 selpg。参照开发 Linux 命令行实用程序

CLI(Command Line Interface)实用程序是Linux下应用开发的基础。正确的编写命令行程序让应用与操作系统融为一体,通过shell或script使得应用获得最大的灵活性与开发效率。例如:

Linux提供了cat、ls、copy等命令与操作系统交互;   
go语言提供一组实用程序完成从编码、编译、库管理、产品发布全过程支持;   
容器服务如docker、k8s提供了大量实用程序支撑云服务的开发、部署、监控、访问等管理任务;   
git、npm等也是大家比较熟悉的工具

尽管操作系统与应用系统服务可视化、图形化,但在开发领域,CLI在编程、调试、运维、管理中提供了图形化程序不可替代的灵活性与效率。
#2、基础知识
selpg是一个unix系统下命令。

该命令本质上就是将一个文件,通过自己设定的分页方式,输出到屏幕或者重定向到其他文件上,或者利用打印机打印出来。
命令行程序主要涉及内容:

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

格式是:
-s start_page -e end_page [ -f | -l lines_per_page ][ -d dest ] [ in_filena

参数
-s,后面接开始读取的页号 int
-e,后面接结束读取的页号 int s和e都要大于1,并且s <= e,否则提示错误s和e都要大于1,并且s <= e,否则提示错误
可选参数:
-l,后面跟行数 int,代表多少行分为一页,不指定 -l 又缺少 -f -则默认按照72行分一页
-f,该标志无参数,代表按照分页符’\f’ 分页
-d,后面接打印机名称,用于打印filename,唯一一个无标识参数,代表选择读取的文件名

#3、代码设计
1.设计selpg结构体
给selpg设计一个结构体,分别保存各个参数的值,使其易于处理。

type Selpg struct {
    Start int //起始页
    End int //结束页
    PageType bool //是否为-f类型命令,是则为真
    Length int //页长度
    Destination string //打印机名称
    Infile string//输入文件
    data []string//储存文件数据的字符串
}

2.利用flag包解析参数,要求用pflag包来替代golang的flag包来获取命令行参数。这里需要 import "github.com/spf13/pflag"

//解析获取参数
func getArgs(args *Selpg) {
    flag.IntVarP(&(args.Start), "startPage", "s", -1, "start page")
    flag.IntVarP(&(args.End), "endPage", "e", -1, "end page")
    flag.IntVarP(&(args.Length), "Length", "l", 72, "the length of page")
    flag.BoolVarP(&(args.PageType), "PageType", "f", false, "page type")
    flag.StringVarP(&(args.Destination), "Destination", "d", "", "print destination")
    flag.Parse()
  
    other := flag.Args() // 其余参数
    if len(other) > 0 {
      args.Infile = other[0]
    } else {
      args.Infile = ""
    }
  }

3.检查命令各参数是否合法,主要是检查起始结束页码是否有输入,是否在合理数值范围内,是否合法,起始页码是否比结束页码小等:

//检查参数合法性
func checkArgs(args *Selpg) {
  if args.Start == -1 || args.End == -1 {
    os.Stderr.Write([]byte("You shouid input like selpg -sNumber -eNumber ... \n"))
    os.Exit(0)
  }

  if args.Start < 1 || args.Start > math.MaxInt32 {
    os.Stderr.Write([]byte("You should input valid start page\n"))
    os.Exit(0)
  }

  if args.End < 1 || args.End > math.MaxInt32 || args.End < args.Start {
    os.Stderr.Write([]byte("You should input valid end page\n"))
    os.Exit(0)
  }

  if (!args.PageType) && (args.Length < 1 || args.Length > math.MaxInt32) {
    os.Stderr.Write([]byte("You should input valid page length\n"))
    os.Exit(0)
  }
}

4.执行命令的过程:
输入: 一旦处理了所有的命令行参数,就使用这些指定的选项以及输入、输出源和目标来开始输入的实际处理。selpg 通过以下方法记住当前页号:如果输入是每页行数固定的,则 selpg 统计新行数,直到达到页长度后增加页计数器。如果输入是换页定界的,则 selpg 改为统计换页符。这两种情况下,只要页计数器的值在起始页和结束页之间这一条件保持为真,selpg 就会输出文本(逐行或逐字)。当那个条件为假(也就是说,页计数器的值小于起始页或大于结束页)时,则 selpg 不再写任何输出。

输出:首先就要区分 -f 类型和 -l 类型,一个搜寻 \f 符号,将文本分隔开,每个作为一页。一个是读取 -l 时计算行数,到指定行数进行分页。在ReadBytes的时候可以根据情况来决定相应的终止符号,读到相应的页(从startPage到endPage)就输出便可。

//执行命令
func processInput(args *Selpg) {

  // read the file
  var reader *bufio.Reader

  if args.Infile == "" {
      reader = bufio.NewReader(os.Stdin)
  } else {
      fileIn, err := os.Open(args.Infile)
      defer fileIn.Close()
      if err != nil {
          os.Stderr.Write([]byte("Open file error\n"))
          os.Exit(0)
      }
      reader = bufio.NewReader(fileIn)
  }
  // output the file
  if args.Destination == "" {
      // 输出到当前命令行
      outputCurrent(reader, args)
  } else {
      // 输出到目的地
      outputToDest(reader, args)
  }
}
func outputCurrent(reader *bufio.Reader, args *Selpg) {
    writer := bufio.NewWriter(os.Stdout)
    lineCtr := 0
    pageCtr := 1

    endSign := '\n'
    if args.PageType == true {
        endSign = '\f'
    }

    for {
        strLine, errR := reader.ReadBytes(byte(endSign))
        if errR != nil {
            if errR == io.EOF {
                writer.Flush()
                break
            } else {
                os.Stderr.Write([]byte("Read bytes from reader fail\n"))
                os.Exit(0)
            }
        }

        if pageCtr >= args.Start && pageCtr <= args.End {
            _, errW := writer.Write(strLine)
            if errW != nil {
                os.Stderr.Write([]byte("Write bytes to out fail\n"))
                os.Exit(0)
            }
        }

        if args.PageType == true {
            pageCtr++
        } else {
            lineCtr++
        }

        if args.PageType != true && lineCtr == args.Length {
            lineCtr = 0
            pageCtr++
            if pageCtr > args.End {
                writer.Flush()
                break
            }
        }
    }
    checkPageNum(args, pageCtr)
}

5.最后是完善其他代码,还要处理-d参数,表示送到相应的打印机打印

func outputToDest(reader *bufio.Reader, args *Selpg) {
    cmd := exec.Command("./" + args.Destination)

    stdin, err := cmd.StdinPipe()
    if err != nil {
        fmt.Println(err)
        os.Exit(0)
    }
    startErr := cmd.Start()
    if startErr != nil {
        fmt.Println(startErr)
        os.Exit(0)
    }

    lineCtr := 0
    pageCtr := 1

    endSign := '\n'
    if args.PageType == true {
        endSign = '\f'
    }

    for {
        strLine, errR := reader.ReadBytes(byte(endSign))
        if errR != nil {
            if errR == io.EOF {
                break
            } else {
                os.Stderr.Write([]byte("Read bytes from reader fail\n"))
                os.Exit(0)
            }
        }

        if args.PageType == true {
            pageCtr++
        } else {
            lineCtr++
        }

        if pageCtr >= args.Start && pageCtr <= args.End {
            _, errW := stdin.Write(strLine)
            if errW != nil {
                fmt.Println(errW)
                os.Stderr.Write([]byte("Write bytes to out fail\n"))
                os.Exit(0)
            }
        }
        if args.PageType != true && lineCtr == args.Length {
            lineCtr = 0
            pageCtr++
            stdin.Write([]byte("\f"))
            if pageCtr > args.End {
                break
            }
        }
    }

    stdin.Close()

    if err := cmd.Wait(); err != nil {
        fmt.Println(err)
        os.Exit(0)
    }

    checkPageNum(args, pageCtr)
}

测试

按文档使用 selpg 章节要求测试你的程序:
in.txt里面是1到100行的内容分别为1到100的输入文件

  1. 首先是 go build selpg.go 编译文件
  2. go run selpg.go
  3. $ selpg -s1 -e1 input_file
  4. $ selpg -s1 -e1 < input_file
  5. $ other_command | selpg -s1 -e1
  6. $ selpg -s1 -e1 input_file >output_file
  7. $ selpg -s1 -e10 input_file 2>error_file
  8. $ selpg -s1 -e10 input_file >output_file 2>error_file
  9. $ selpg -s1 -e1 input_file | other_command
  10. $ selpg -s1 -e11 input_file 2>error_file | other_command
  11. $ selpg -s1 -e3 -l4 input_file’’
  12. $ selpg -s1 -e3 -f input_file
  13. 测试-dXXX
购物商城项目采用PHP+mysql有以及html+css jq以及layer.js datatables bootstorap等插件等开发,采用了MVC模式,建立一个完善的电商系统,通过不同用户的不同需求,进行相应的调配和处理,提高对购买用户进行配置….zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计,皆可应用在项目、毕业设计、课程设计、期末/期/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值