服务计算 | Selpg Golang实现

selpg介绍

本文介绍开发selpg(SELect PaGes),一个类似于catlspr等标准命令的Linux命令行实用程序。selpg允许用户指定从输入文本抽取的页的范围,这些输入文本可以来自文件或者另一个进程,然后将输入进行打印操作。由于运行环境为CentOS7系统的虚拟机,并且并没有实际的打印机,所以我们需要在虚拟机中安装虚拟打印机。具体方法参见传送门

命令行准则

在实现selpg的过程中,我们需要掌握命令行的标准,从而可以开发出更加对用户更加有好的,确保用户以更加灵活的方式使用该程序。

主要准则包括输入、输出、错误输出、执行以及命令行参数。简要介绍:

输入

$ command input_file		# 从input_file获取标准输入(stdin)
$ command 					# 从终端获取标准输入
$ command < input_file		# 利用 < 重定向标准输入,从input_file获取输入
$ other_command | command 	# 利用 | (pipe)使得标准输入来自另一个程序的标准输出

输出

$ command					# 标准输出(stdout)输出到终端
$ command > output_file		# 利用 > 重定向标准输出到output_file
$ command | other_command	# 利用 | (pipe)将标准输出成为另一命令的标准输入

错误输出

$ command							# 标准错误(stderr)被输出到终端
$ command 2>error_file				# 标准错误重定向到error_file
$ command >output_file 2>error_file	# 标准输出和标准错误重都定向到文件
$ command 2>&1						# 将标准错误发送至标准输出被重定向的任何位置,此命令中标准错输出为终端,所以标准错误也为终端
$ command >output_file 2>&1			# 将标准错误和标准输出都被定向到output_file

执行

不管程序的输入源(文件、管道或终端)和输出目的地是什么,程序都应该以同样的方式工作。这使得在如何使用它方面有最大的灵活性。

命令行参数

作为选项的命令行参数右前缀“-”或者“–”标识;另一类参数是那些不是选项的参数,并不改变程序的行为,这类参数代表要处理的文件名等等。

Linux 实用程序语法图看起来如下:

$ command mandatory_opts [ optional_opts ] [ other_args ]

其中:

  • command 是命令本身的名称。
  • mandatory_opts 是为使命令正常工作必须出现的选项列表。
  • optional_opts 是可指定也可不指定的选项列表,这由用户来选择;但是,其中一些参数可能是互斥的。
  • other_args 是命令要处理的其它参数的列表;这可以是任何东西,而不仅仅是文件名。

以上内容整理来自开发Linux命令行实用程序

selpg参数介绍

参数(值) 是否必须 介绍
s(start_page_num) 必须 表示从输入数据的第start_page_num开始
e(end_page_num) 必须 表示从输入数据的第end_page_num结束
l(page_len) 非必须 表示输入数据每page_len行为1页
f 非必须 表示输入数据以\f为换页符
d(Destination) 非必须 Destination是lp命令的“-d”选项可接受的打印目的地名称;即selpg命令加上-d参数之后,表示Destination打印机打印输入数据。若要验证该选项是否已经生效,可以运行命令“lpstat -t”,查看添加到“Destination”打印队列是否有此打印作业。如果当前打印机可用,则会打印该输出,这一特性是用popen()系统调用来实现的,该系统调用允许一个进程打开到另一个进程的管道,将管道用于输出或输入。

Golang实现

数据结构

type Selpg_Args struct {
	start_page *int			// -s参数值(开始页面数)
	end_page   *int			// -e参数值(结束页面数量)
	page_len    *int		// -l参数值(以page_len行为1页)
	page_type_f *bool		// -f参数值(以\f为分页符) 
	output      *string		// -o参数值(输出文件名)(可忽略)
	input       string		// 输入文件名
	dest_print  *string		// -d参数值(打印目的地)
}

参数解析

利用pflag第三方库可以方便处理参数情况,有关flag和pflag库介绍详见传送门

func initial() {
	// selpg_args.page_len = 72
	// 添加shorthand参数(去掉方法后面的字母P即取消shorthand参数)
	selpg_args.start_page = pflag.IntP("start_page", "s", -1, "start page")
	selpg_args.end_page = pflag.IntP("end_page", "e", -1, "end page")
	selpg_args.page_type_f = pflag.BoolP("form_feed", "f", false, "delimited by form feeds")
	selpg_args.page_len = pflag.IntP("limit", "l", 72, "delimited by fixed page length")
	selpg_args.output = pflag.StringP("output", "o", "", "output filename")
	selpg_args.dest_print = pflag.StringP("dest", "d", "", "target printer")
	// 另外一种写法
	// pflag.IntVarP(selpg_args.start_page, "start_page", "s", -1, "start page")
	// pflag.IntVarP(selpg_args.end_page, "end_page", "e", -1, "end page")
	// pflag.BoolVarP(selpg_args.page_type_f, "form_feed", "f", false, "delimited by form feeds")
	// pflag.IntVarP(selpg_args.page_len, "limit", "l", 72, "delimited by fixed page length")
	// pflag.StringVarP(selpg_args.output, "output", "o", "", "output filename")
	// pflag.StringVarP(selpg_args.dest_print, "dest", "d", "", "target printer")
	selpg_args.input = ""
	pflag.Usage = usage
	pflag.Parse()
}

// usage 函数定义如下:
func usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, os.Args[0]+" -sstart_page_num -eend_page_num [ -f | -llines_per_page ] [ -doutput ] [ input ]\n")
	fmt.Fprintln(os.Stderr, "[OPTIONS]:")
	fmt.Fprintln(os.Stderr, "   filename string       input filename")
    pflag.PrintDefaults()	// PrintDefaults()表示输出默认的提示信息
}

校验参数

func handle_args() {
    /* if len(os.Args) < 3, then we can know that -s or -e is not entered. */
    /* if *selpg_args_.start_page == -1, then arg -s isn't entered, because -s-1 is not allowed(pflag can't analysis). */
	if len(os.Args) < 3 || *selpg_args.start_page == -1 || *selpg_args.end_page == -1 {
        fmt.Println("You are expected to input the start_page_num and end_page_num which will be greater than zero")
		pflag.Usage()
		os.Exit(1)
	}
	/* judge the start_page_num and end_page_num */
    /* INT_MAX definition: const INT_MAX = int(^uint(0) >> 1) */
	if *selpg_args.start_page <= 0 {
		checkErr(errors.New("The start_page_num can't be less than or equal to zero"))
	}
	if *selpg_args.start_page > INT_MAX {
		checkErr(errors.New("The start_page_num can't be greater than INT_MAX: " + strconv.Itoa(INT_MAX)))
	}
	if *selpg_args.end_page <= 0 {
		checkErr(errors.New("The end_page_num can't be less than or equal to zero"))
	}
	if *selpg_args.end_page > INT_MAX {
		checkErr(errors.New("The end_page_num can't be greater than INT_MAX: " + strconv.Itoa(INT_MAX)))
	}
	if *selpg_args.start_page > *selpg_args.end_page {
		checkErr(errors.New("The end_page_num can't be greater than start_page_num"))
	}

	/* judge the page_len */
	if *selpg_args.page_len <= 0 {
		checkErr(errors.New("The -l limit can't be less than or equal to zero"))
	}

	for _, arg := range os.Args[1:] {
        /* in the following judgements we can also use method: strings.HasPrefix() */
		/* judge the option -f */
		if matched, _ := regexp.MatchString(`^-f`, arg); matched {
			if len(arg) > 2 {
				checkErr(errors.New(os.Args[0] + ": option should be only \"-f\""))
			}
		}
		/* judge the dest printer */
		if matched, _ := regexp.MatchString(`^-d`, arg); *selpg_args.dest_print == "" && matched {
			checkErr(errors.New(os.Args[0] + ": option -d requires a printer destination\n"))
		}
		/* judge the output file */
		if matched, _ := regexp.MatchString(`^-o`, arg); *selpg_args.output == "" && matched {
			checkErr(errors.New(os.Args[0] + ": option -o requires a output filename\n"))
		}
		/* store the input filename */
		if arg[0] != '-' {
			selpg_args.input = arg
			break
		}
	}
}

处理命令输入

func handle_input() {
    /* define InputFile = os.Stdin or *os.File */
	var InputFile *os.File
	if selpg_args.input != "" {
		var err error
		InputFile, err = os.Open(selpg_args.input)
		if err != nil {
			fmt.Fprintf(os.Stderr, "An error occurred on opening the inputfile: %s\nDoes the file exist?\n", selpg_args.input)
			os.Exit(1)
		}
	} else {
		InputFile = os.Stdin
	}
	defer InputFile.Close()	// close the file before the end of the function
	
	var OutputFile *os.File
	var cmd *exec.Cmd
	if *selpg_args.output != "" {
		var err error
		OutputFile, err = os.Create(*selpg_args.output)
		checkErr(err)
	} else if *selpg_args.dest_print != "" {
		fmt.Printf("Printer: %s\n", *selpg_args.dest_print)
		cmd = exec.Command("lp", "-d", *selpg_args.dest_print)
        /* the command below can also work */
		// cmd = exec.Command("lpr", "-P", *selpg_args.dest_print) 
	} else {
		OutputFile = os.Stdout
	}
	defer OutputFile.Close()
	
    /* use io.Pipe to create the pipe between input(including reading from file and stdin) and output(including writing to file, stdout and printing the data) */
	r, w := io.Pipe()
    /* create the go routine to read from stdin or file and put the data to PipeWriter*/
	go readFile(w, InputFile)
    /* define buffer to read from the PipeReader */
	buf := new(bytes.Buffer)
	buf.ReadFrom(r)
    /* cmd != nil denotes that *selpg_args.dest_print != "" and we need to print the input data */
	if cmd != nil {
        /* define the reader and assign it to cmd.Stdin which denotes the input of the command(lp -ddest_print) */
		cmd.Stdin = strings.NewReader(buf.String())
		var outErr bytes.Buffer
		cmd.Stderr = &outErr
        /* execute the command(lp -ddest_print) */
		err := cmd.Run()
		if err != nil {
			checkErr(errors.New(fmt.Sprint(err) + ": " + outErr.String()))
		}
	}
    /* if we use argument -o, then we can write the input data to a file */
	if OutputFile != nil {
		OutputFile.WriteString(buf.String())
	}
}

readFile函数

func readFile(w *io.PipeWriter, InputFile *os.File) {
    /* define the reader */
	inputReader := bufio.NewReader(InputFile)
    /* default delimit = '\n' */
	delimit := '\n'
	pages := 0
	lines := 0
	inputString := ""
	if *selpg_args.page_type_f {
		delimit = '\f'
	}
	for {
        /* we use the delimit as the delimiter to read from the file */
		inputSubString, readerError := inputReader.ReadString(byte(delimit))
        /* handle the input, replace all form feeds '\f' by '', then our print will not be influenced */
		inputSubString = strings.Replace(inputSubString, "\f", "", -1)
        /* inputString will store one page's content */
		inputString += inputSubString
        /* when delimiter is '\n', then we need to count the lines until lines are equal to *selpg_args.page_len, then we can print the page. */
		if delimit == '\n' {
			lines++
			if lines == *selpg_args.page_len {
				pages++
				lines = 0
                /* if pages is within the expected scope, we write it to PipeWriter. */
				if pages >= *selpg_args.start_page && pages < *selpg_args.end_page {
					print_pages++
					fmt.Fprint(w, inputString+"\f")
					inputString = ""
				}
			}
		} else {	/* the delimiter is '\f' */
			pages++
			if pages >= *selpg_args.start_page && pages < *selpg_args.end_page {
				print_pages++
				fmt.Fprint(w, inputString+"\f")
				inputString = ""
			}
		}
        /* the final page */
		if pages >= *selpg_args.end_page {
            print_pages++
			fmt.Fprint(w, inputString)
			break
		}
        /* the end of the file */
		if readerError == io.EOF {
			if inputString != "" && inputString != "\n" && inputString != "\f" {
				print_pages++
                fmt.Fprint(w, inputString)
			}
			break
		}
	}
    /* close the PipeWriter */ 
	w.Close()
}

程序测试

输入文件input.txt如下:(按照换行符一共14行,按照换页符一共7页)
在这里插入图片描述
测试文件输入,控制台输出

$ ./selpg -s1 -e1 input_file
$ ./selpg -s1 -e1 < input_file

在这里插入图片描述

$ other_command | selpg -s1 -e1

在这里插入图片描述

$ ./selpg -s1 -e2 input_file >output_file 2>error_file

将第1页第二页写到标准输出,所有的错误信息被shell /内核重定向到“error_file”,2和>之间不能有空格。

在这里插入图片描述
在这里插入图片描述

$ ./selpg -s1 -e2 input_file >output_file 2>/dev/null

selpg 将第 1 页到第 2 页写至标准输出,标准输出被重定向至“output_file”;selpg 写至标准错误的所有内容都被重定向至 /dev/null(空设备),这意味着错误消息被丢弃了。设备文件 /dev/null 废弃所有写至它的输出,当从该设备文件读取时,会立即返回 EOF。

$ ./selpg -s1 -e2 input_file >/dev/null

selpg 将第 10 页到第 20 页写至标准输出,标准输出被丢弃;错误消息在屏幕出现。

$ ./selpg -s1 -e2 input_file 2>error_file | other_command

在这里插入图片描述

$ ./selpg -s1 -e2 -l2 input_file

在这里插入图片描述

$ ./selpg -s1 -e2 -f input_file

由输入文件,前三行为第一页,接下来两行为第二页
在这里插入图片描述

$ ./selpg -s1 -e2 -dCups-PDF input_file

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

$ ./selpg -s1 -e2 input_file > output_file 2>error_file &

该命令利用了 Linux 的一个强大特性,即:在“后台”运行进程的能力。在这个例子中发生的情况是:“进程标识”(pid)如 1234 将被显示,然后 shell 提示符几乎立刻会出现,使得您能向 shell 输入更多命令。同时,selpg 进程在后台运行,并且标准输出和标准错误都被重定向至文件。这样做的好处是您可以在 selpg 运行时继续做其它工作。
在这里插入图片描述
在这里插入图片描述
可以使用ps命令来查看该进程的状态,由于瞬间完成,所以并没有看到该进程。
至此,selpg命令已经基本实现。

猜你喜欢

转载自blog.csdn.net/liuyh73/article/details/83021556