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。
提示:
- 請按文檔使用 selpg章節要求測試你的程序
- 請使用 pflag 替代 goflag 以滿足 Unix 命令行規範,參考:Golang之使用Flag和Pflag
- golang 文件讀寫、讀環境變量,請自己查 os 包
- “-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 可以處理兩種輸入文本:
- 該類文本的頁行數固定。
- 該類型文本的頁由 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)
}
}
}
讀入文件以及分頁的處理
- 讀取文件,若有文件名則需要檢查文件是否存在。
- 分頁處理,若有-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 一節中的命令進行測試。