【服務計算】CLI命令行實用程序開發基礎

GoOnline項目鏈接

http://139.9.57.167:20080/share/bmdna7676kvpt2sh9b10?secret=false

概述

CLI(Command Line Interface)實用程序是Linux下應用開發的基礎。正確的編寫命令行程序讓應用與操作系統融爲一體,通過shell或script使得應用獲得最大的靈活性與開發效率。Linux提供了cat、ls、copy等命令與操作系統交互;go語言提供一組實用程序完成從編碼、編譯、庫管理、產品發佈全過程支持;容器服務如docker、k8s提供了大量實用程序支撐雲服務的開發、部署、監控、訪問等管理任務;git、npm等都是大家比較熟悉的工具。儘管操作系統與應用系統服務可視化、圖形化,但在開發領域,CLI在編程、調試、運維、管理中提供了圖形化程序不可替代的靈活性與效率。

實驗要求

使用 golang 開發 開發 Linux 命令行實用程序 中的 selpg。

提示:

  1. 請按文檔使用 selpg章節要求測試你的程序
  2. 請使用 pflag 替代 goflag 以滿足 Unix 命令行規範,參考:Golang之使用Flag和Pflag
  3. golang 文件讀寫、讀環境變量,請自己查 os 包
  4. “-dXXX” 實現,請自己查 os/exec 庫,例如案例 Command,管理子進程的標準輸入和輸出通常使用 io.Pipe,具體案例見 Pipe

selpg程序邏輯

selpg 是從文本輸入選擇頁範圍的實用程序。該輸入可以來自作爲最後一個命令行參數指定的文件,在沒有給出文件名參數時也可以來自標準輸入。

selpg 首先處理所有的命令行參數。在掃描了所有的選項參數(也就是那些以連字符爲前綴的參數)後,如果 selpg 發現還有一個參數,則它會接受該參數爲輸入文件的名稱並嘗試打開它以進行讀取。如果沒有其它參數,則 selpg 假定輸入來自標準輸入。

參數處理

“-sNumber”和“-eNumber”強制選項

selpg 要求用戶用兩個命令行參數“-sNumber”(例如,“-s10”表示從第 10 頁開始)和“-eNumber”(例如,“-e20”表示在第 20 頁結束)指定要抽取的頁面範圍的起始頁和結束頁。

$ selpg -s10 -e20 ...

“-lNumber”和“-f”可選選項

selpg 可以處理兩種輸入文本:

  1. 該類文本的頁行數固定。
  2. 該類型文本的頁由 ASCII 換頁字符(十進制數值爲 12,在 C 中用“\f”表示)定界。
$ selpg -s10 -e20 -166 ...

$ selpg -s10 -e20 -f ...

“-dDestination”可選選項

selpg 還允許用戶使用“-dDestination”選項將選定的頁直接發送至打印機。這裏,“Destination”應該是 lp 命令“-d”選項(請參閱“man lp”)可接受的打印目的地名稱。

$ selpg -s10 -e20 -dlp1 ...

輸入處理

一旦處理了所有的命令行參數,就使用這些指定的選項以及輸入、輸出源和目標來開始輸入的實際處理。

selpg 通過以下方法記住當前頁號:如果輸入是每頁行數固定的,則 selpg 統計新行數,直到達到頁長度後增加頁計數器。如果輸入是換頁定界的,則 selpg 改爲統計換頁符。這兩種情況下,只要頁計數器的值在起始頁和結束頁之間這一條件保持爲真,selpg 就會輸出文本(逐行或逐字)。當那個條件爲假(也就是說,頁計數器的值小於起始頁或大於結束頁)時,則 selpg 不再寫任何輸出。

實驗過程

pflag安裝

在終端使用go get命令進行安裝。

go get -u github.com/spf13/pflag

定義保存參數的結構體selpg_args

// 用於保存參數的結構體
type selpgArgs struct {
	start_page  int    // 起始頁
	end_page    int    // 結束頁
	page_length int    // 頁行數固定的文本
	page_type   bool   // 頁由換行符定界的文本
	dest        string // 將選定的頁直接發送至打印機
	filename    string // 輸入文件名
}

使用pflag包進行輸入參數的處理

通過使用pflag.IntVarP()pflag.StringVarP()方法進行參數值的綁定;通過使用pflag.Parse()方法讓pflag對標識和參數進行解析。

// 初始化參數
func getArgs(args *selpgArgs) {
	// 參數值的綁定
	pflag.IntVarP(&args.start_page, "start_page", "s", -1, "Start page of file")
	pflag.IntVarP(&args.end_page, "end_page", "e", -1, "End page of file")
	pflag.IntVarP(&args.page_length, "page_len", "l", 72, "Number of rows in one page")
	pflag.BoolVarP(&args.page_type, "page_type", "f", false, "Flag splits page")
	pflag.StringVarP(&args.dest, "dest", "d", "", "name of printer")

	// 打印到屏幕上
	pflag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: selpg -s startPage -e endPage [-l linePerPage | -f ][-d dest] filename\n\n")
		pflag.PrintDefaults()
	}
	// 標識和參數的解析
	pflag.Parse()

}

參數檢查

檢查參數的輸入是否符合規範。

若不符合規範,則打印錯誤類型並終止進程;若符合規範,則打印到屏幕上。

// 檢查參數
func checkArgs(args *selpgArgs) {
	if args.start_page < 1 || args.end_page < 1 || args.start_page > args.end_page {
		// 輸出命令行的提示信息
		pflag.Usage()
	}

	if args.start_page < 1 || args.end_page < 1 {
		fmt.Fprintf(os.Stderr, "\n[Error] The start page and end page are required and must be bigger than 0!\n")
		os.Exit(1)
	}

	if args.start_page > args.end_page {
		fmt.Fprintf(os.Stderr, "[Error] The start page number can't be bigger than the end page number!\n")
		os.Exit(2)
	}

	if (args.page_type == true) && (args.page_length != 72) {
		fmt.Fprintf(os.Stderr, "\n[Error]The command -l and -f are exclusive!\n")
		os.Exit(3)
	}

	if args.page_length < 1 {
		fmt.Fprintf(os.Stderr, "[Error] The page length must be bigger than 1!\n")
		os.Exit(4)
	}

	// Narg()返回解析後參數
	if pflag.NArg() > 0 {
		args.filename = pflag.Arg(0)
		_, err := os.Stat(args.filename)
		if err != nil {
			fmt.Fprintf(os.Stderr, "[Error]Wrong filepath!\n")
			os.Exit(5)
		}
	}
}

讀入文件以及分頁的處理

  1. 讀取文件,若有文件名則需要檢查文件是否存在。
  2. 分頁處理,若有-f參數則根據分頁符進行分頁。
// 處理分頁
func handle(args *selpgArgs) {
	filein := os.Stdin
	fileout := os.Stdout
	lineCount := 0
	pageCount := 1

	if args.filename != "" { // 若給出文件名,則需檢查文件是否存在
		err := errors.New("")
		filein, err = os.Open(args.filename)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Open file failed!\n")
			os.Exit(6)
		}
		// defer後面的函數在defer語句所在的函數執行結束的時候會被調用
		defer filein.Close() // 關閉文件
	}

	// 開始根據是否以換頁符分頁進行分頁
	readLine := bufio.NewReader(filein)
	if args.page_type == false {
		for {
			line, err := readLine.ReadString('\n')
			if err == io.EOF {
				break
			}
			if err != nil {
				fmt.Fprintf(os.Stderr, "Read file error!\n")
				os.Exit(7)
			}
			lineCount++
			if lineCount > args.page_length {
				pageCount++
				lineCount = 1
			}
			if pageCount >= args.start_page && pageCount <= args.end_page {
				fmt.Fprintf(fileout, "%s", line)
			}
		}
	} else {
		for {
			page, err := readLine.ReadString('\f')
			if err == io.EOF {
				break
			}
			if err != nil {
				fmt.Fprintf(os.Stderr, "Read file error!\n")
				os.Exit(7)
			}
			pageCount++
			if pageCount >= args.start_page && pageCount <= args.end_page {
				fmt.Fprintf(fileout, "%s", page)
			}
		}
	}
	cmd := exec.Command("cat", "-n")
	_, err := cmd.StdinPipe()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Create pipe error\n")
		os.Exit(8)
	}
	if args.dest != "" {
		cmd.Stdout = fileout
		cmd.Run()
	}
	filein.Close()
	fileout.Close()
}

實驗結果

運行

在GOPATH文件路徑處新建selpg文件夾,將selpg.go文件放入其中。終端執行命令go install selpg即可在全局使用selpg命令。

測試

測試文件

在這裏插入圖片描述

測試結果

參照 使用selpg 一節中的命令進行測試。

selpg -s1 -e1 test.txt

在這裏插入圖片描述

selpg -s1 -e1 < test.txt

在這裏插入圖片描述

selpg -s1 -e1 test.txt > output.txt

在這裏插入圖片描述

selpg -s1 -e1 in.txt 2 > error.txt

在這裏插入圖片描述

selpg -s1 -e1 -l 10 test.txt

在這裏插入圖片描述

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