CLI(Command Line Interface)实用程序是Linux下应用开发的基础。正确的编写命令行程序让应用与操作系统融为一体,通过shell或script使得应用获得最大的灵活性与开发效率。Linux提供了cat、ls、copy等命令与操作系统交互;go语言提供一组实用程序完成从编码、编译、库管理、产品发布全过程支持;容器服务如docker、k8s提供了大量实用程序支撑云服务的开发、部署、监控、访问等管理任务;git、npm等都是大家比较熟悉的工具。尽管操作系统与应用系统服务可视化、图形化,但在开发领域,CLI在编程、调试、运维、管理中提供了图形化程序不可替代的灵活性与效率。
这里内容主要参考于开发 Linux 命令行实用程序,详情可参见文章。
selpg 是从文本输入选择页范围的实用程序。该输入可以来自作为最后一个命令行参数指定的文件,在没有给出文件名参数时也可以来自标准输入
“-sNumber”和“-eNumber”强制选项:
selpg 要求用户用两个命令行参数“-sNumber”和“-eNumber”指定要抽取的页面范围的起始页和结束页,它们必须是命令行上在命令名 selpg 之后的头两个参数。selpg会对所给的页号进行合理性检查。即它会检查两个数字是否为有效的正整数以及结束页是否不小于起始页。
$ selpg -s10 -e20 ...
代表从第 10 页开始,在第 20 页结束。
“-lNumber”和“-f”可选选项:
“-lNumber”定义页的固定长度和页由页行数定界,“-f”该类型文本的页由 ASCII 换页字符(十进制数值为 12,在 C 中用“\f”表示)定界。“-lNumber”和“-f”选项是互斥的,它们不能共同存在。它们是缺省类型,因此不必给出选项进行说明。也就是说,如果既没有给出“-lNumber”也没有给出“-f”选项,则 selpg 会理解为页有固定的长度。
$ selpg -s10 -e20 -l66 ...
代表页有固定长度,每页为 66 行
$ selpg -s10 -e20 -f ...
该命令告诉 selpg 在输入中寻找换页符,并将其作为页定界符处理。
“-dDestination”可选选项:
selpg 还允许用户使用“-dDestination”选项将选定的页直接发送至打印机。这里,“Destination”应该是 lp 命令“-d”选项可接受的打印目的地名称。
selpg -s10 -e20 -dlp1
该命令将选定的页作为打印作业发送至 lp1 打印目的地。
实现的代码地址:https://github.com/wutao123456789/Homework
此次代码的实现主要依据在开发 Linux 命令行实用程序中给出的selpg.c文件编写。
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"github.com/spf13/pflag"
)
其中pflag使用的是github上的公开库,需要首先下载到本地,具体的安装方法请点击。
type selpg_args struct {
start_page int
end_page int
in_filename string
dest string
page_len int
page_type int
}
var progname string //程序名
const INT_MAX = int(^uint(0) >> 1) //将所有位数取0,再取反,最后右移一位。如:0000 取反 1111 后移一位 0111
func (arg *selpg_args)process_args() {
progname = os.Args[0]
pflag.IntVarP(&arg.start_page, "start_page", "s", 0, "start page")
pflag.IntVarP(&arg.end_page, "end_page", "e", 0, "end page")
pflag.IntVarP(&arg.page_type, "page_type", "f", 0, "Page type")
pflag.IntVarP(&arg.page_len, "page_len", "l", 36, "page len")
pflag.StringVarP(&arg.dest, "dest", "d", "", "dest")
pflag.Parse()
file_arr := pflag.Args()
arg.in_filename =""
count := 0
for{
if arg.in_filename =="" && file_arr[count] != "<"{
arg.in_filename = file_arr[count]
}
if file_arr[count] == ">"|| file_arr[count] == "|"{
count++
arg.dest = file_arr[count]
}
count++
if count >= len(file_arr){
break
}
}
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "%s: please input enough arguments\n", progname)
usage()
os.Exit(1)
}
if arg.start_page < 1 || arg.start_page > INT_MAX {
fmt.Fprintf(os.Stderr, "%s: please input positive integer for start_page\n", progname)
usage()
os.Exit(2)
}
if arg.end_page < 1 || arg.end_page > (INT_MAX-1) || arg.end_page < arg.start_page {
fmt.Fprintf(os.Stderr, "%s: please input positive integer for end_page or start_page should not be greater than end_page\n", progname)
usage()
os.Exit(3)
}
if arg.page_len < 1 || arg.page_len > (INT_MAX-1) {
fmt.Fprintf(os.Stderr, "%s: please input positive integer for page_len\n", progname)
pflag.Usage()
os.Exit(4)
}
}
进行参数的绑定,使得数据结构arg能够获取输入参数的值,并通过pflag.Args()获取非参数的值。并进行合理性判断,确保输入了强制性参数和输入值的有效性。
func (arg selpg_args)process_input() {
var read *bufio.Reader
var write *bufio.Writer
if arg.in_filename == "" {
read = bufio.NewReader(os.Stdin)
} else {
fin, err := os.Open(arg.in_filename)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: could not open input file %s\n", progname, arg.in_filename)
os.Exit(5)
}
read = bufio.NewReader(fin)
defer fin.Close()
}
if arg.dest == "" {
write = bufio.NewWriter(os.Stdout)
} else {
fin1, err := os.Open(arg.dest)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: could not open output file %s\n", progname, arg.dest)
os.Exit(6)
}
write = bufio.NewWriter(fin1)
defer fin1.Close()
}
line_number, page_number, pLen := 1, 1, arg.page_len
judge_Flag := '\n'
if arg.page_type==1 {
judge_Flag = '\f'
pLen = 1
}
for {
line,err:= read.ReadString(byte(judge_Flag));
if err != nil && len(line) == 0 {
break
}
if line_number > pLen {
page_number++
line_number = 1
}
if page_number >= arg.start_page && page_number <= arg.end_page {
write.Write([]byte(line))
}
line_number++
}
if page_number < arg.end_page {
fmt.Fprintf(os.Stderr,
"\n%s: put exist error\n", progname)
}
}
首先判断从那读取数据,是命令行还是文件。然后判断是将数据输出到屏幕上还是输出到文件当中。确定页的界定符,如果按行数则获取限定每页的行数;如果根据’\f’判断,则出现一次为一页。最后根据当前页数与计划的结束页数对比判断输出是否成功。
由于使用的为goland进行测试,所以是通过编译输入参数来实现测试。
-$ selpg -s1 -e1 < input.txt