1. 程式人生 > >服務計算 - 3 Golang開發Linux命令列實用程式 - selpg

服務計算 - 3 Golang開發Linux命令列實用程式 - selpg

文章目錄

Golang開發Linux命令列實用程式 - selpg

1. 介紹

2. 設計與實現

2.1 設計思路

  • 理解selpg命令的功能以及處理流程。

  • selpg命令涉及的基本操作

    • 檔案的讀寫
    • 從終端獲取輸入以及在終端輸出

2.2 功能模組劃分與實現

  • 構建命令列引數的結構體,由於引數使用的比較多,可以使用一個結構體儲存起來,使用起來比較方便

    type selpg_args struct {
    	startPage      int
    endPage int inFile string pageLen int pageType bool // true for -f, false for -lNumber outDestination string }
  • 引數解析

    • 使用pflag包對命令列輸入引數進行解析
    • 使用 pflag 替代 goflag 以滿足 Unix 命令列規範, 參考:Golang之使用Flag和Pflag
    • 獲得flag引數後pflag.Parse()函式才能把引數解析出來
    • 使用pflag.Args()
      來獲取未被標記的引數
    // 解析獲取引數
    func getArgs(args *selpg_args) {
      pflag.IntVarP(&(args.startPage), "startPage", "s", -1, "start page")
      pflag.IntVarP(&(args.endPage), "endPage", "e", -1, "end page")
      pflag.IntVarP(&(args.pageLen), "pageLen", "l", 72, "the length of page")
      pflag.BoolVarP(&(args.pageType), "pageType", "f", false, "page type")
      pflag.StringVarP(&(args.outDestination), "outDestination", "d", "", "print destination")
      pflag.Parse()
    
      other := pflag.Args() // 其餘引數
      if len(other) > 0 {
        args.inFile = other[0]
      } else {
        args.inFile = ""
      }
    }
    
  • 引數檢查

    • 檢查輸入引數的合法性

      • 是否輸入了起始頁和結束頁
      • 起始頁大於1小於結束頁以及不能溢位(MaxInt32
      • 結束頁大於起始頁並且不能溢位(MaxInt32
    • 遇到不合法則輸出錯誤同時結束程式

      // 檢查引數合法性
      func checkArgs(args *selpg_args) {
        if args.startPage == -1 || args.endPage == -1 {
          os.Stderr.Write([]byte("You shouid input like selpg -sNumber -eNumber ... \n"))
          os.Exit(0)
        }
      
        if args.startPage < 1 || args.startPage > math.MaxInt32 {
          os.Stderr.Write([]byte("You should input valid start page\n"))
          os.Exit(0)
        }
      
        if args.endPage < 1 || args.endPage > math.MaxInt32 || args.endPage < args.startPage {
          os.Stderr.Write([]byte("You should input valid end page\n"))
          os.Exit(0)
        }
      
        if (!args.pageType) && (args.pageLen < 1 || args.pageLen > math.MaxInt32) {
          os.Stderr.Write([]byte("You should input valid page length\n"))
          os.Exit(0)
        }
      }
      
  • 執行命令

    • 執行命令部分主要涉及到檔案的讀取以及寫入或者標準輸入的獲取以及命令列下輸出,我們使用到bufio包,至於-dXXX 實現,使用到 os/exec包

    • bufio包介紹與使用

      • bufio包實現了帶快取的 I/O 操作,它封裝一個 io.Readerio.Writer 物件,使其具有快取和一些文字讀寫功能

        • func NewReaderSize(rd io.Reader, size int) *Reader

          NewReaderSizerd 封裝成一個帶快取的 bufio.Reader 物件,快取大小由 size 指定(如果小於 16 則會被設定為 16)。如果rd的基型別就是有足夠快取的 bufio.Reader 型別,則直接將rd 轉換為基型別返回。

        • func NewReaderSize(rd io.Reader, size int) *Reader

          NewReader相當於 NewReaderSize(rd, 4096)

        • func (reader *Reader) ReadBytes(delim byte) (line []byte, err error)

          ReadBytesreader中查詢delim並讀出 delim 及其之前的所有資料。如果 ReadBytes 在找到 delim 之前遇到錯誤,則返回遇到錯誤之前的所有資料,同時返回遇到的錯誤(通常是 io.EOF)。 只有當 ReadBytes 找不到 delim時,err 才不為 nil

    • os/exec包介紹與使用

      • os/exec包執行外部命令。它包裝了os.StartProcess函式以便更容易的修正輸入和輸出,使用管道連線I/O,以及作其它的一些調整。

        • func Command(name string, arg ...string) *Cmd

          command返回cmd結構來執行帶有相關引數的命令,它僅僅設定cmd結構中的Path和Args引數,如果name引數中不包含路徑分隔符,command使用LookPath來解決路徑問題,否則的話就直接使用name;Args直接跟在command命令之後,所以在Args中不許要新增命令。我們用該命令建立一個命令物件,引數為子程序路徑和子程序引數(可選)

          // 指定執行的程式,實現模擬的印表機
          cmd := exec.Command("./" + args.outDestination)
          
        • func (c *Cmd) StderrPipe() (io.ReadCloser, error)

          StderrPipe返回一個pipe,這個管道連線到command的標準錯誤,當command命令退出時,Wait將關閉這些pipe

        • func (c *Cmd) StdinPipe() (io.WriteCloser, error)

          StdinPipe返回一個連線到command標準輸入的管道pipe。我們可以通過在此處寫入傳輸資訊,然後作為子程序的標準輸入。

      • 對於-dXXX的實現,建立一個子程序,讓其模擬印表機,使用管道將資料傳輸給子程序,子程序讀取管道內傳輸的資訊並且打印出來。

    • 處理流程

      • 判斷是否指定輸入檔案,為空則將標準輸入作為輸入,否則為檔案流

      • 判斷是否為-d型別

      • 判斷輸入型別為-l還是-f,依據要求讀取輸入,在輸出到標準輸出

      • 判斷起始頁數以及結束頁數是否滿足實際標準

        // 執行命令
        func processInput(args *selpg_args) {
        
          // read the file
          var reader *bufio.Reader
        
          if args.inFile == "" {
            reader = bufio.NewReader(os.Stdin)
          } else {
            fileIn, err := os.Open(args.inFile)
            defer fileIn.Close()
            if err != nil {
              os.Stderr.Write([]byte("Open file error\n"))
              os.Exit(0)
            }
            reader = bufio.NewReader(fileIn)
          }
        
          // output the file
          if args.outDestination == "" {
            // 輸出到當前命令列
            outputCurrent(reader, args)
          } else {
            // 輸出到目的地
            outputToDest(reader, args)
          }
        }
        
        // 輸出到當前命令列
        func outputCurrent(reader *bufio.Reader, args *selpg_args);
        
        // 輸出到指定目的地
        func outputToDest(reader *bufio.Reader, args *selpg_args);
        
    • 具體程式碼參見Github專案

4 參考文獻