服務計算 第四周
Go語言開發CLI 命令行實用程序
1.題目要求
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
包。需要注意的是,pflag
是flag
的升級版本,因此需要安裝,安裝地址爲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
部分測試
-
- selpg -s1 -e1 input_file
selpg --s 1 --e 1 data.txt
由於過長,中間不顯示,之後的截圖中不再完全顯示,只顯示代表片段
-
- selpg -s1 -e1 < input_file
selpg --s 1 --e 1 < data.txt
-
- 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文件
-
- 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
-
- 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
-
- 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
丟棄了錯誤信息
-
- selpg -s10 -e20 -l66 input_file
- 爲了方便觀察,使用
l3
selpg --s 10 --e 20 --l 3 data.txt
-
- selpg -s10 -e20 -dlp1 input_file
爲了方便,顯示
10~15
頁,每頁3行
selpg --s 10 --e 15 --l 3 --d lp1 data.txt
-
- selpg -s10 -e20 -f input_file
爲了方便顯示,輸出
10-12
頁
selpg --s 10 --e 12 --f data.txt