服務計算 第四周 Go語言開發CLI命令實用程序


1.題目要求

在這裏插入圖片描述

CLI要求見此


2.使用材料

2.1開發環境

終於,最後還是使用了CentOs7桌面版(真香)。VsCode真的好用。
貼出桌面安裝步驟

yum groupinstall "GNOME Desktop"
ln -sf /lib/systemd/system/runlevel5.target /etc/systemd/system/default.target

分別是安裝桌面,以及將桌面設置爲開機啓動。
根據情況加sudo指令
除此之外,還安裝了 g++用以生成測試文檔代碼編寫。


2.2包

使用了bufio io os用作讀取,輸入輸出
使用了pflag包。需要注意的是,pflagflag的升級版本,因此需要安裝,安裝地址爲github.com/spf13/pflag

go get github.com/spf13/pflag

直接獲取,即可使用,非常快捷。


3.框架構建

3.1編程思路

按照C語言實現selpg的思路來看,還是非常簡單的。大致可以分成以下幾個步驟:
讀取指令 -> 分析指令 -> 設置參數 -> 判斷參數合法 -> 執行指令
同樣的,我們也可以按照同樣的思路來實現:
讀取指令 -> 分析指令並設置參數 -> 判斷參數的合法性 -> 執行指令


3.2編寫框架

指令描述

USAGE: ./selpg [--s start] [--e end] [--l lines | --f ] [ --d dest ] [ in_filename ]

 selpg --s start    : start page
 selpg --e end      : end page
 selpg --l lines    : lines/page
 selpg --f          : check page with '\f'
 selpg --d dest     : pipe destination
  • 由於使用了pflag,指令默認方式變爲 --s 20 而不是 -s20

3.2.1結構

首先,我們需要構造我們的結構,根據我們能夠使用的參數,設置了幾個變量

type args struct {
	start int				//起始頁碼
	end int					//終止頁碼
	length int				//行數
	readType string			//‘l’代表按照行數計算頁;‘f’爲按照換頁符計算頁
	dest string				//定向位置
	inputType string		//輸入方式
}

3.2.2參數綁定

將所有的參數使用pflag綁定到變量上。

	sa := new(args)

	//參數綁定變量
	flag.IntVar(&sa.start, "s", 0, "the start Page")				//開始頁碼,默認爲0
	flag.IntVar(&sa.end, "e", 0, "the end Page")					//結束頁碼,默認爲0
	flag.IntVar(&sa.length, "l", 72, "the length of the page")		//每頁行數,默認爲72行每頁
	flag.StringVar(&sa.dest, "d", "", "the destiny of printing")	//輸出位置,默認爲空字符

3.2.3分析指令並設置參數

主要分析兩個參數:輸入方式(文件輸入還是鍵盤輸入)、讀取方式(l / f 設置每頁行數還是以換頁符爲準)

	//設置讀取方式
	isF := flag.Bool("f", false, "")
	flag.Parse()

	//如果輸入f,按照f並取-1;否則按照 l
	if *isF {
		sa.readType = "f"
		sa.length = -1
	} else {
		sa.readType = "l"
	}

	//如果使用了文件輸入,將方式置爲文件名
	sa.inputType = ""
	if flag.NArg() == 1 {
		sa.inputType = flag.Arg(0)
	}

3.2.4判斷參數是否合法

判斷剩餘參數、l/f是否同時出現、起始頁與終止頁是否衝突

	//檢查剩餘參數數量
	if narg := flag.NArg(); narg != 1 && flag.NArg() != 0 {
		usage()
		os.Exit(1)
	}
	//檢查起始終止頁
	if sa.start > sa.end || sa.start < 1 {
		usage()
		os.Exit(1)
	}
	//檢查l f 是否同時出現
	if sa.readType == "f" && sa.length != -1 {
		usage()
		os.Exit(1)
	}

3.2.5執行指令

根據參數執行指令
流程爲:判斷輸入方式,並將輸入流綁定 -> 如果有管道,綁定管道 -> l/f讀取

	//初始化
	fin := os.Stdin					//輸入
	fout := os.Stdout				//輸出
	currentLine := 0				//當前行
	currentPage := 1				//當前頁
	var inpipe io.WriteCloser		//管道
	var err error					//錯誤

	//判斷輸入方式
	if sa.inputType != "" {
		fin, err = os.Open(sa.inputType)
		if err != nil {
			fmt.Fprintf(os.Stderr, "ERROR: Can't find input file \"%s\"!\n", sa.inputType)
			//fmt.Println(err)
			usage()
			os.Exit(1)
		}
		defer fin.Close()			//全部結束了再關閉
	}

	//確定輸出到文件或者輸出到屏幕
	//通過用管道接通grep模擬打印機測試,結果輸出到屏幕
	if sa.dest != "" {
		cmd := exec.Command("grep", "-nf", "keyword")
		inpipe, err = cmd.StdinPipe()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		defer inpipe.Close() //最後執行
		cmd.Stdout = fout
		cmd.Start()
	}

	//分頁方式
	//設置行數
	if sa.readType == "l" {
		//按照行讀取
		line := bufio.NewScanner(fin)
		for line.Scan() {
			if currentPage >= sa.start && currentPage <= sa.end {
				//輸出到窗口
				fout.Write([]byte(line.Text() + "\n"))
				if sa.dest != "" {
					//定向到文件管道
					inpipe.Write([]byte(line.Text() + "\n"))
				}
			}
			currentLine++
			//翻頁
			if currentLine == sa.length {
				currentPage++
				currentLine = 0
			}
		}
	} else {
		//用換行符 '\f'分頁 
		rd := bufio.NewReader(fin)
		for {
			page, ferr := rd.ReadString('\f')
			if ferr != nil || ferr == io.EOF {
				if ferr == io.EOF {
					if currentPage >= sa.start && currentPage <= sa.end {
						fmt.Fprintf(fout, "%s", page)
					}
				}
				break
			}
			//'\f'翻頁
			page = strings.Replace(page, "\f", "", -1)
			currentPage++
			if currentPage >= sa.start && currentPage <= sa.end {
				fmt.Fprintf(fout, "%s", page)
			}
		}
	}

3.2.6USAGE

輸入錯誤提示信息,直接寫上去Fprintfln即可

	fmt.Fprintf(os.Stderr, "\nUSAGE: ./selpg [--s start] [--e end] [--l lines | --f ] [ --d dest ] [ in_filename ]\n")
	fmt.Fprintf(os.Stderr, "\n selpg --s start    : start page")
	fmt.Fprintf(os.Stderr, "\n selpg --e end      : end page")
	fmt.Fprintf(os.Stderr, "\n selpg --l lines    : lines/page")
	fmt.Fprintf(os.Stderr, "\n selpg --f          : check page with '\\f'")
	fmt.Fprintf(os.Stderr, "\n selpg --d dest     : pipe destination\n")

4測試

4.1生成測試文件

使用了C++來生成文件,具體如下:

/*
	生成一個測試文件
	文件共有500行,每行格式爲: line + 行號
	行號從1開始,直到500
	每10行有一個換頁符'\f'將行號隔開,因此共有50頁
	輸出在data.txt文件中
*/
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main () {
	ofstream fout;
	fout.open("data.com");
	string line = "line ";
	string page = "\f";
	for (int i = 0; i < 50; i++) {
		for (int j = 0; j < 10; j++) {
			fout << line << i * 50 + j + 1 << endl;
		}
		fout << page;
	}
}

生成文件

g++ create.cpp
./a.out

4.2樣例測試

使用selpg -> 使用selpg部分測試

    1. selpg -s1 -e1 input_file
selpg --s 1 --e 1 data.txt

在這裏插入圖片描述

在這裏插入圖片描述

由於過長,中間不顯示,之後的截圖中不再完全顯示,只顯示代表片段

    1. selpg -s1 -e1 < input_file
selpg --s 1 --e 1 < data.txt

在這裏插入圖片描述

    1. selpg -s10 -e20 input_file >output_file

頁數有限,取3 - 5

selpg --s 3 --e 5 data.txt > output1.txt 
touch output1.txt
selpg --s 3 --e 5 data.txt >output1.txt
vim output1.txt

打開output1.txt文件

在這裏插入圖片描述

    1. selpg -s10 -e20 input_file 2>error_file
touch err.txt
selpg --s 10 --e 20 data.txt 2>err.txt
vim err.txt

文件應該放不下,因此打開err.txt

在這裏插入圖片描述

    1. selpg -s10 -e20 input_file >output_file 2>error_file

同樣選取3 - 5

touch output2.txt
selpg --s 3 --5 data.txt >output2.txt 2>err.txt
vim output2.txt
vim err.txt

output2.txt

在這裏插入圖片描述

    1. selpg -s10 -e20 input_file >output_file 2>/dev/null
touch output3.txt
selpg --s 10 --e 20 data.txt >output3.txt 2>/dev/null
vim output3.txt

丟棄了錯誤信息

在這裏插入圖片描述

    1. selpg -s10 -e20 -l66 input_file
  • 爲了方便觀察,使用l3
selpg --s 10 --e 20 --l 3 data.txt

在這裏插入圖片描述

    1. selpg -s10 -e20 -dlp1 input_file

爲了方便,顯示10~15頁,每頁3行

selpg --s 10 --e 15 --l 3 --d lp1 data.txt

在這裏插入圖片描述

    1. selpg -s10 -e20 -f input_file

爲了方便顯示,輸出10-12

selpg --s 10 --e 12 --f data.txt

在這裏插入圖片描述


5源碼地址

git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章