服務(wù)計(jì)算
概述
CLI(Command Line Interface)實(shí)用程序是Linux下應(yīng)用開(kāi)發(fā)的基礎(chǔ)襟铭。正確的編寫(xiě)命令行程序讓?xiě)?yīng)用與操作系統(tǒng)融為一體盹憎,通過(guò)shell或script使得應(yīng)用獲得最大的靈活性與開(kāi)發(fā)效率姥闭。例如:
- Linux提供了cat褐健、ls缸血、copy等命令與操作系統(tǒng)交互秉剑;
- go語(yǔ)言提供一組實(shí)用程序完成從編碼泛豪、編譯、庫(kù)管理侦鹏、產(chǎn)品發(fā)布全過(guò)程支持诡曙;
- 容器服務(wù)如docker、k8s提供了大量實(shí)用程序支撐云服務(wù)的開(kāi)發(fā)略水、部署价卤、監(jiān)控、訪問(wèn)等管理任務(wù)渊涝;
- git慎璧、npm等也是大家比較熟悉的工具。
- 盡管操作系統(tǒng)與應(yīng)用系統(tǒng)服務(wù)可視化跨释、圖形化胸私,但在開(kāi)發(fā)領(lǐng)域,CLI在編程鳖谈、調(diào)試岁疼、運(yùn)維、管理中提供了圖形化程序不可替代的靈活性與效率缆娃。
基礎(chǔ)知識(shí)
幾乎所有語(yǔ)言都提供了完善的 CLI 實(shí)用程序支持工具捷绒。以下是一些入門(mén)文檔(c 語(yǔ)言):
如果你熟悉 python :
閱讀以后你應(yīng)該知道 POSIX/GNU 命令行接口的一些概念與規(guī)范贯要。命令行程序主要涉及內(nèi)容:
- 命令
- 命令行參數(shù)
- 選項(xiàng):長(zhǎng)格式暖侨、短格式
- IO:stdin、stdout崇渗、stderr字逗、管道函荣、重定向
- 環(huán)境變量
Golang的支持
使用os,flag包扳肛,最簡(jiǎn)單處理參數(shù)的代碼:
package main
import (
"fmt"
"os"
)
func main() {
for i, a := range os.Args[1:] {
fmt.Printf("Argument %d is %s\n", i+1, a)
}
}
我們先運(yùn)行一下:
使用flag包的代碼:
package main
import (
"flag"
"fmt"
)
func main() {
var port int
flag.IntVar(&port, "p", 8000, "specify port to use. defaults to 8000.")
flag.Parse()
fmt.Printf("port = %d\n", port)
fmt.Printf("other args: %+v\n", flag.Args())
}
運(yùn)行結(jié)果:
中文參考:
更多代碼實(shí)踐:
4傻挂、開(kāi)發(fā)實(shí)踐
使用 golang 開(kāi)發(fā) 開(kāi)發(fā) Linux 命令行實(shí)用程序 中的 selpg
提示:
- 請(qǐng)按文檔 使用 selpg 章節(jié)要求測(cè)試你的程序
- 請(qǐng)使用 pflag 替代 goflag 以滿足 Unix 命令行規(guī)范, 參考:Golang之使用Flag和Pflag
- golang 文件讀寫(xiě)挖息、讀環(huán)境變量金拒,請(qǐng)自己查 os 包
- “-dXXX” 實(shí)現(xiàn),請(qǐng)自己查
os/exec
庫(kù)套腹,例如案例 Command绪抛,管理子進(jìn)程的標(biāo)準(zhǔn)輸入和輸出通常使用io.Pipe
,具體案例見(jiàn) Pipe - 請(qǐng)自帶測(cè)試程序电禀,確保函數(shù)等功能正確
在做這次任務(wù)之前幢码,我們需要先下載plfag的包
本次任務(wù)的實(shí)質(zhì)是把selpg.c文件中的代碼用golang實(shí)現(xiàn),了解完內(nèi)容我們就開(kāi)始實(shí)現(xiàn)吧
首先定義數(shù)據(jù)類(lèi)型:
type selpgArgs struct {
startPage int //開(kāi)始頁(yè)
endPage int //結(jié)束頁(yè)
inFilename string //輸入文件的名字
pageLen int //頁(yè)長(zhǎng)
pageType bool //是否按頁(yè)結(jié)束符計(jì)算
printDest string //打印地址
}
基本的數(shù)據(jù)類(lèi)型和C中的并沒(méi)有什么區(qū)別尖飞,唯一改變的是pageType的類(lèi)型症副,這里選擇使用bool型。
我們先來(lái)看一下main函數(shù):
main函數(shù)主要做了以下幾個(gè)工作:
獲取參數(shù)政基、檢查參數(shù)贞铣、執(zhí)行命令
func main() {
progname = os.Args[0] //progname為程序名
var a selpgArgs
pflag.IntVarP(&a.startPage, "startPage", "s", -1, "Start Page")
pflag.IntVarP(&a.endPage, "endPage", "e", -1, "End page")
pflag.BoolVarP(&a.pageType, "pageType", "f", false, "Page type")
pflag.IntVarP(&a.pageLen, "pageLen", "l", 20, "Lines per page")
pflag.StringVarP(&a.printDest, "printDest", "d", "", "Destination")
pflag.Parse() //將命令行參數(shù)加入到綁定的變量
a.inFilename = ""
if b := pflag.Args(); len(b) > 0 {
a.inFilename = b[0]
}
processArgs(a) //對(duì)參數(shù)進(jìn)行判斷
processInput(a) //執(zhí)行指令
}
檢查參數(shù)
需要對(duì)輸入的參數(shù)進(jìn)行判斷是否滿足輸入要求
func processArgs(a selpgArgs) {
//參數(shù)小于3個(gè)報(bào)錯(cuò)
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "%s:please input enough arguments\n", progname)
usage()
os.Exit(1)
}
//對(duì)開(kāi)始頁(yè)進(jìn)行判斷是否滿足要求
if a.startPage < 1 || a.startPage > intMax {
fmt.Fprintf(os.Stderr, "%s:please input right integer for startPage\n", progname)
usage()
os.Exit(2)
}
//對(duì)結(jié)束頁(yè)判斷是否滿足要求
if a.endPage < 1 || a.endPage > (intMax-1) || a.endPage < a.startPage {
fmt.Fprintf(os.Stderr, "%s:please input right integer for endPage\n", progname)
usage()
os.Exit(3)
}
//對(duì)頁(yè)長(zhǎng)進(jìn)行判斷是否滿足要求
if a.pageLen < 1 || a.pageLen > (intMax-1) {
fmt.Fprintf(os.Stderr, "%s:please input right integer for pageLen\n", progname)
usage()
os.Exit(4)
}
//對(duì)文件名進(jìn)行判斷是否滿足要求
if a.inFilename != "" {
if _, err := os.Stat(a.inFilename); os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "%s: input file \"%s\" does not exist\n", progname, a.inFilename)
usage()
os.Exit(5)
}
}
}
執(zhí)行指令
下面是定義的兩個(gè)參數(shù)
var read *bufio.Reader //讀取輸入
var write io.WriteCloser //寫(xiě)入輸出
使用 os 包 進(jìn)行 golang 文件讀寫(xiě)、讀環(huán)境變量
如果用戶輸入了inFilename沮明,就將文件作為輸入辕坝,否則將命令行作為輸入
if a.inFilename == "" {
read = bufio.NewReader(os.Stdin)
} else {
file, err := os.Open(a.inFilename)
if err != nil {
fmt.Fprintf(os.Stderr, "%s:could not open input file %s\n", progname, a.inFilename)
os.Exit(6)
}
read = bufio.NewReader(file)
defer file.Close()
}
緊接著是輸出,如果輸入命令中沒(méi)有目的地選項(xiàng)荐健,則輸出默認(rèn)為標(biāo)準(zhǔn)輸出酱畅。
首先通過(guò)exec.Command創(chuàng)建了一個(gè)子進(jìn)程,執(zhí)行打印江场,打印的目的地為輸入命令中的目的地纺酸,然后我們將程序的輸出流writer設(shè)為打印進(jìn)程的輸入管道
if a.printDest == "" {
write = os.Stdout
} else {
cmd := exec.Command("lp", "-d"+a.printDest)
var err error
if write, err = cmd.StdinPipe(); err != nil {
fmt.Fprintf(os.Stderr, "%s: could not open pipe to \"%s\"\n", progname, a.printDest)
fmt.Println(err)
os.Exit(7)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = cmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "%s: cmd start error\n", progname)
fmt.Println(err)
os.Exit(8)
}
}
下面將指定頁(yè)范圍的數(shù)據(jù)送入輸出地址
lineNumber, pageNumber, pageL := 1, 1, a.pageLen
flag := '\n'
if a.pageType == true {
flag = '\f'
pageL = 1
}
//使用reade讀取所有頁(yè)的數(shù)據(jù),并將要求范圍內(nèi)的頁(yè)寫(xiě)入write
for {
line, err := read.ReadString(byte(flag))
if err != nil && len(line) == 0 {
break
}
if lineNumber > pageL {
pageNumber++
lineNumber = 1
}
if pageNumber >= a.startPage && pageNumber <= a.endPage {
_, err := write.Write([]byte(line))
if err != nil {
fmt.Println(err)
os.Exit(9)
}
}
lineNumber++
}
最后檢查一下讀取是否成功以及是否完成
if pageNumber < a.startPage {
fmt.Fprintf(os.Stderr, "\n%s: start_page (%d) greater than total pages (%d),"+" no output written\n", progname, a.startPage, pageNumber)
} else if pageNumber < a.endPage {
fmt.Fprintf(os.Stderr, "\n%s: end_page (%d) greater than total pages (%d),"+" less output than expected\n", progname, a.endPage, pageNumber)
}
usage 函數(shù)會(huì)輸出 selpg 命令的格式扛稽,當(dāng)用戶輸入有誤時(shí)的提示使用吁峻。
func usage() {
fmt.Fprintf(os.Stderr, "\nUSAGE: %s -sstartPage -eendPage [ -f | -llinesPerPage ] [ -dprintDest ] [ inFilename ]\n", progname)
}
測(cè)試程序
先安裝程序
go install server-computing/selpg
selpg -s1 -e1 input.txt
該命令將把“input.txt”的第 1 頁(yè)寫(xiě)至標(biāo)準(zhǔn)輸出(也就是屏幕)滑负,因?yàn)檫@里沒(méi)有重定向或管道在张。
selpg -s1 -e1 < input.txt
該命令與示例 1 所做的工作相同,但在本例中矮慕,selpg 讀取標(biāo)準(zhǔn)輸入帮匾,而標(biāo)準(zhǔn)輸入已被 shell/內(nèi)核重定向?yàn)閬?lái)自“input.txt”而不是顯式命名的文件名參數(shù)。輸入的第 1 頁(yè)被寫(xiě)至屏幕痴鳄。
more input.txt | selpg -s1 -e2
“other_command”的標(biāo)準(zhǔn)輸出被 shell/內(nèi)核重定向至 selpg 的標(biāo)準(zhǔn)輸入瘟斜。將第 1 頁(yè)到第 2 頁(yè)寫(xiě)至 selpg 的標(biāo)準(zhǔn)輸出(屏幕)。
selpg -s1 -e2 input.txt > output.txt
selpg 將第 1 頁(yè)到第 2 頁(yè)寫(xiě)至標(biāo)準(zhǔn)輸出;標(biāo)準(zhǔn)輸出被 shell/內(nèi)核重定向至“output.txt”螺句。
selpg -s10 -e20 input.txt 2>error.txt
selpg 將第 10 頁(yè)到第 20 頁(yè)寫(xiě)至標(biāo)準(zhǔn)輸出(屏幕)虽惭;所有的錯(cuò)誤消息被 shell/內(nèi)核重定向至“error_file”。請(qǐng)注意:在“2”和“>”之間不能有空格蛇尚;這是 shell 語(yǔ)法的一部分(請(qǐng)參閱“man bash”或“man sh”)芽唇。
selpg -s10 -e20 input.txt >output.txt 2>error.txt
selpg 將第 10 頁(yè)到第 20 頁(yè)寫(xiě)至標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)輸出被重定向至“output.txt”取劫;selpg 寫(xiě)至標(biāo)準(zhǔn)錯(cuò)誤的所有內(nèi)容都被重定向至“error.txt”匆笤。當(dāng)“input.txt”很大時(shí)可使用這種調(diào)用;您不會(huì)想坐在那里等著 selpg 完成工作谱邪,并且您希望對(duì)輸出和錯(cuò)誤都進(jìn)行保存炮捧。
selpg -s5 -e7 input.txt >output.txt 2>/dev/null
selpg 將第 5 頁(yè)到第 7 頁(yè)寫(xiě)至標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)輸出被重定向至“output.txt”惦银;selpg 寫(xiě)至標(biāo)準(zhǔn)錯(cuò)誤的所有內(nèi)容都被重定向至 /dev/null(空設(shè)備)咆课,這意味著錯(cuò)誤消息被丟棄了。設(shè)備文件 /dev/null 廢棄所有寫(xiě)至它的輸出扯俱,當(dāng)從該設(shè)備文件讀取時(shí)傀蚌,會(huì)立即返回 EOF。
selpg -s10 -e20 input.txt >/dev/null
selpg 將第 10 頁(yè)到第 20 頁(yè)寫(xiě)至標(biāo)準(zhǔn)輸出蘸吓,標(biāo)準(zhǔn)輸出被丟棄善炫;錯(cuò)誤消息在屏幕出現(xiàn)。這可作為測(cè)試 selpg 的用途库继,此時(shí)您也許只想(對(duì)一些測(cè)試情況)檢查錯(cuò)誤消息箩艺,而不想看到正常輸出。
selpg -s1 -e2 input.txt | wc
selpg 的標(biāo)準(zhǔn)輸出透明地被 shell/內(nèi)核重定向宪萄,成為“other_command”的標(biāo)準(zhǔn)輸入艺谆,第 10 頁(yè)到第 20 頁(yè)被寫(xiě)至該標(biāo)準(zhǔn)輸入“萦ⅲ“other_command”的示例可以是 lp静汤,它使輸出在系統(tǒng)缺省打印機(jī)上打印【有祝“other_command”的示例也可以 wc虫给,它會(huì)顯示選定范圍的頁(yè)中包含的行數(shù)、字?jǐn)?shù)和字符數(shù)侠碧∧ü溃“other_command”可以是任何其它能從其標(biāo)準(zhǔn)輸入讀取的命令。錯(cuò)誤消息仍在屏幕顯示弄兜。
selpg -s10 -e20 input.txt 2>error.txt | wc
與上面的示例 9 相似药蜻,只有一點(diǎn)不同:錯(cuò)誤消息被寫(xiě)至“error.txt”瓷式。
selpg -s1 -e2 -l33 input.txt
該命令將頁(yè)長(zhǎng)設(shè)置為 33 行,這樣 selpg 就可以把輸入當(dāng)作被定界為該長(zhǎng)度的頁(yè)那樣處理语泽。第 1 頁(yè)到第 2 頁(yè)被寫(xiě)至 selpg 的標(biāo)準(zhǔn)輸出(屏幕)贸典。
selpg -s1 -e2 -f input_file
假定頁(yè)由換頁(yè)符定界。第 1 頁(yè)到第 2 頁(yè)被寫(xiě)至 selpg 的標(biāo)準(zhǔn)輸出(屏幕)踱卵。