CLI 命令行實(shí)用程序開(kāi)發(fā)基礎(chǔ)

服務(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

提示:

  1. 請(qǐng)按文檔 使用 selpg 章節(jié)要求測(cè)試你的程序
  2. 請(qǐng)使用 pflag 替代 goflag 以滿足 Unix 命令行規(guī)范, 參考:Golang之使用Flag和Pflag
  3. golang 文件讀寫(xiě)挖息、讀環(huán)境變量金拒,請(qǐng)自己查 os 包
  4. “-dXXX” 實(shí)現(xiàn),請(qǐng)自己查 os/exec 庫(kù)套腹,例如案例 Command绪抛,管理子進(jìn)程的標(biāo)準(zhǔn)輸入和輸出通常使用 io.Pipe,具體案例見(jiàn) Pipe
  5. 請(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)輸出(屏幕)踱卵。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瓤漏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子颊埃,更是在濱河造成了極大的恐慌蔬充,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件班利,死亡現(xiàn)場(chǎng)離奇詭異饥漫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)罗标,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)庸队,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人闯割,你說(shuō)我怎么就攤上這事彻消。” “怎么了宙拉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵宾尚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我谢澈,道長(zhǎng)煌贴,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任锥忿,我火速辦了婚禮牛郑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘敬鬓。我一直安慰自己淹朋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布钉答。 她就那樣靜靜地躺著础芍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪希痴。 梳的紋絲不亂的頭發(fā)上者甲,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天春感,我揣著相機(jī)與錄音砌创,去河邊找鬼虏缸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嫩实,可吹牛的內(nèi)容都是我干的刽辙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼甲献,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宰缤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起晃洒,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤慨灭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后球及,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體氧骤,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年吃引,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筹陵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镊尺,死狀恐怖朦佩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庐氮,我是刑警寧澤语稠,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站弄砍,受9級(jí)特大地震影響颅筋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜输枯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一议泵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桃熄,春花似錦先口、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至螟深,卻和暖如春谐宙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背界弧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工凡蜻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搭综,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓划栓,卻偏偏與公主長(zhǎng)得像兑巾,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忠荞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348