1. 程式人生 > >服務計算(1) Go 語言開發 selpg 命令列實用程式

服務計算(1) Go 語言開發 selpg 命令列實用程式

參考

原始碼


/*=================================================================

Program name:
	selpg (SELect PaGes)

Purpose:
	Sometimes one needs to extract only a specified range of
pages from an input text file. This program allows the user to do
that.

===================================================================*/
package main /*================================= imports =======================*/ import ( "fmt" "os" "syscall" "os/exec" "io" "bufio" "strings" flag "github.com/ogier/pflag" ) /*================================= types =========================*/ type sp_args struct { start_page int end_page int
in_filename string page_len int page_type bool print_dest string } /*================================= globals =======================*/ var sa = new(sp_args) /*================================= init() ========================*/ func init() { flag.IntVarP(&sa.start_page, "s", "s", -1
, "start page") flag.IntVarP(&sa.end_page, "e", "e", -1, "end page") flag.IntVarP(&sa.page_len, "l", "l", 72, "lines per page") flag.BoolVarP(&sa.page_type, "f", "f", false, "form-feed-delimited") flag.StringVarP(&sa.print_dest, "d", "d", "", "destination") flag.Usage = usage } /*================================= main()=========================*/ func main() { flag.Parse() process_args() process_input() } /*================================= process_args() ================*/ func process_args() { if flag.NFlag() < 1 { /* handle mandatory args first */ } else if sa.start_page < 1 { fmt.Fprintf(os.Stderr, "invalid start page %v\n", sa.start_page) } else if sa.end_page < 1 || sa.end_page < sa.start_page { fmt.Fprintf(os.Stderr, "invalid end page %v\n", sa.end_page) } else if !sa.page_type && sa.page_len < 1 { fmt.Fprintf(os.Stderr, "invalid page length %v\n", sa.page_len) } else { /* while there more args and they start with a '-' */ if flag.NArg() > 0 { sa.in_filename = flag.Arg(0) if syscall.Access(sa.in_filename, syscall.O_RDONLY) != nil { fmt.Fprintf(os.Stderr, "input file \"%s\" does not exist or cannot be read\n", sa.in_filename) } else { return } } else { return } } flag.Usage() os.Exit(1) } /*================================= process_input() ===============*/ func process_input() { fin := os.Stdin /* input stream */ fout := os.Stdout /* output stream */ line_ctr := 0 /* line counter */ page_ctr := 1 /* page counter */ var inpipe io.WriteCloser var err error /* set the input source */ if sa.in_filename != "" { fin, err = os.Open(sa.in_filename) } /* set the output destination */ if sa.print_dest != "" { cmd := exec.Command("lp", "-d", sa.print_dest) inpipe, err = cmd.StdinPipe() if err != nil { fmt.Fprintf(os.Stderr, "could not open pipe to \"%s\"\n", sa.print_dest) flag.Usage() os.Exit(1) } cmd.Stdout = fout //???? cmd.Start() } /* begin one of two main loops based on page type */ if sa.page_type { reader := bufio.NewReader(fin) for { page, rerr := reader.ReadString('\f') if page_ctr >= sa.start_page { page = strings.Replace(page, "\f", "", -1) if sa.print_dest != "" { fmt.Fprintf(inpipe, "%s", page) } else { fmt.Fprintf(fout, "%s", page) } } page_ctr++ if rerr == io.EOF || page_ctr > sa.end_page { break } } } else { line := bufio.NewScanner(fin) for line.Scan() { if page_ctr >= sa.start_page { if sa.print_dest != "" { fmt.Fprintf(inpipe, "%s\n", line.Text()) } else { fmt.Fprintf(fout, "%s\n", line.Text()) } } line_ctr++ if line_ctr == sa.page_len { line_ctr = 0 page_ctr++ if page_ctr > sa.end_page { break } } } } /* end main loop */ if page_ctr < sa.start_page { fmt.Fprintf(os.Stderr, "start_page (%d) greater than total pages (%d), no output written\n", sa.start_page, page_ctr) } else if page_ctr < sa.end_page { fmt.Fprintf(os.Stderr, "end_page (%d) greater than total pages (%d), less output than expected\n", sa.end_page, page_ctr) } else { /* it was EOF, not error */ fin.Close() if sa.print_dest != "" { inpipe.Close() } fmt.Fprintf(os.Stderr, "done\n"); } } /*================================= usage() =======================*/ func usage() { fmt.Fprintf(os.Stderr, ` Usage: ./selpg [-s start_page] [-e end_page] [ -f | -l lines_per_page ] [ -d dest ] [ in_filename ] Options: `) flag.PrintDefaults() } /*================================= EOF ===========================*/

設計說明

該實用程式從標準輸入或從作為命令列引數給出的檔名讀取文字輸入。它允許使用者指定來自該輸入並隨後將被輸出的頁面範圍。例如,如果輸入含有 100 頁,則使用者可指定只打印第 35 至 65 頁。這種特性有實際價值,因為在印表機上列印選定的頁面避免了浪費紙張。另一個示例是,原始檔案很大而且以前已列印過,但某些頁面由於印表機卡住或其它原因而沒有被正確列印。在這樣的情況下,則可用該工具來只打印需要列印的頁面。

該輸入可以來自作為最後一個命令列引數指定的檔案,在沒有給出檔名引數時也可以來自標準輸入。

selpg 首先處理所有的命令列引數。在掃描了所有的選項引數(也就是那些以連字元為字首的引數)後,如果 selpg 發現還有一個引數,則它會接受該引數為輸入檔案的名稱並嘗試開啟它以進行讀取。如果沒有其它引數,則 selpg 假定輸入來自標準輸入。

引數處理

“-sNumber”和“-eNumber”強制選項: selpg 要求使用者用兩個命令列引數“-sNumber”(例如,“-s10”表示從第 10 頁開始)和“-eNumber”(例如,“-e20”表示在第 20 頁結束)指定要抽取的頁面範圍的起始頁和結束頁。selpg 對所給的頁號進行合理性檢查;換句話說,它會檢查兩個數字是否為有效的正整數以及結束頁是否不小於起始頁。這兩個選項,“-sNumber”和“-eNumber”是強制性的,而且必須是命令列上在命令名 selpg 之後的頭兩個引數:

$ selpg -s10 -e20 ...

(… 是命令的餘下部分,下面對它們做了描述)。

“-lNumber”和“-f”可選選項: selpg 可以處理兩種輸入文字:

型別 1: 該類文字的頁行數固定。這是預設型別,因此不必給出選項進行說明。也就是說,如果既沒有給出“-lNumber”也沒有給出“-f”選項,則 selpg 會理解為頁有固定的長度(每頁 72 行)。

選擇 72 作為預設值是因為在行印表機上這是很常見的頁長度。這樣做的意圖是將最常見的命令用法作為預設值,這樣使用者就不必輸入多餘的選項。該預設值可以用“-lNumber”選項覆蓋,如下所示:

$ selpg -s10 -e20 -l66 ...

這表明頁有固定長度,每頁為 66 行。

型別 2: 該型別文字的頁由 ASCII 換頁字元(十進位制數值為 12,在 C 中用“\f”表示)定界。該格式與“每頁行數固定”格式相比的好處在於,當每頁的行數有很大不同而且檔案有很多頁時,該格式可以節省磁碟空間。在含有文字的行後面,型別 2 的頁只需要一個字元 ― 換頁 ― 就可以表示該頁的結束。印表機會識別換頁符並自動根據在新的頁開始新行所需的行數移動列印頭。

將這一點與型別 1 比較:在型別 1 中,檔案必須包含 PAGELEN - CURRENTPAGELEN 個新的行以將文字移至下一頁,在這裡 PAGELEN 是固定的頁大小而 CURRENTPAGELEN 是當前頁上實際文字行的數目。在此情況下,為了使列印頭移至下一頁的頁首,印表機實際上必須列印許多新行。這在磁碟空間利用和印表機速度方面效率都很低(儘管實際的區別可能不太大)。

型別 2 格式由“-f”選項表示,如下所示:

$ selpg -s10 -e20 -f ...

該命令告訴 selpg 在輸入中尋找換頁符,並將其作為頁定界符處理。

注:“-lNumber”和“-f”選項是互斥的。

“-dDestination”可選選項: selpg 還允許使用者使用“-dDestination”選項將選定的頁直接傳送至印表機。這裡,“Destination”應該是 lp 命令“-d”選項(請參閱“man lp”)可接受的列印目的地名稱。該目的地應該存在 ― selpg 不檢查這一點。在運行了帶“-d”選項的 selpg 命令後,若要驗證該選項是否已生效,請執行命令“lpstat -t”。該命令應該顯示新增到“Destination”列印佇列的一項列印作業。如果當前有印表機連線至該目的地並且是啟用的,則印表機應列印該輸出。這一特性是用 popen() 系統呼叫實現的,該系統呼叫允許一個程序開啟到另一個程序的管道,將管道用於輸出或輸入。在下面的示例中,我們開啟到命令

$ lp -dDestination

的管道以便輸出,並寫至該管道而不是標準輸出:

$ selpg -s10 -e20 -dlp1

該命令將選定的頁作為列印作業傳送至 lp1 列印目的地。您應該可以看到類似“request id is lp1-6”的訊息。該訊息來自 lp 命令;它顯示列印作業標識。如果在執行 selpg 命令之後立即執行命令 lpstat -t | grep lp1 ,您應該看見 lp1 佇列中的作業。如果在執行 lpstat 命令前耽擱了一些時間,那麼您可能看不到該作業,因為它一旦被列印就從佇列中消失了。

輸入處理

一旦處理了所有的命令列引數,就使用這些指定的選項以及輸入、輸出源和目標來開始輸入的實際處理。 selpg 通過以下方法記住當前頁號:如果輸入是每頁行數固定的,則 selpg 統計新行數,直到達到頁長度後增加頁計數器。如果輸入是換頁定界的,則 selpg 改為統計換頁符。這兩種情況下,只要頁計數器的值在起始頁和結束頁之間這一條件保持為真,selpg 就會輸出文字(逐行或逐字)。當那個條件為假(也就是說,頁計數器的值小於起始頁或大於結束頁)時,則 selpg 不再寫任何輸出。

使用

為了演示終端使用者可以如何應用我們所介紹的一些原則,下面給出了可使用的 selpg 命令字串示例:

$ selpg -s1 -e1 input_file

該命令將把“input_file”的第 1 頁寫至標準輸出(也就是螢幕),因為這裡沒有重定向或管道。

$ selpg -s1 -e1 < input_file

該命令與示例 1 所做的工作相同,但在本例中,selpg 讀取標準輸入,而標準輸入已被 shell/核心重定向為來自“input_file”而不是顯式命名的檔名引數。輸入的第 1 頁被寫至螢幕。

$ other_command | selpg -s10 -e20

“other_command”的標準輸出被 shell/核心重定向至 selpg 的標準輸入。將第 10 頁到第 20 頁寫至 selpg 的標準輸出(螢幕)。

$ selpg -s10 -e20 input_file >output_file

selpg 將第 10 頁到第 20 頁寫至標準輸出;標準輸出被 shell/核心重定向至“output_file”。

$ selpg -s10 -e20 input_file 2>error_file

selpg 將第 10 頁到第 20 頁寫至標準輸出(螢幕);所有的錯誤訊息被 shell/核心重定向至“error_file”。請注意:在“2”和“>”之間不能有空格;這是 shell 語法的一部分(請參閱“man bash”或“man sh”)。

$ selpg -s10 -e20 input_file >output_file 2>error_file

selpg 將第 10 頁到第 20 頁寫至標準輸出,標準輸出被重定向至“output_file”;selpg 寫至標準錯誤的所有內容都被重定向至“error_file”。當“input_file”很大時可使用這種呼叫;您不會想坐在那裡等著 selpg 完成工作,並且您希望對輸出和錯誤都進行儲存。

$ selpg -s10 -e20 input_file >output_file 2>/dev/null

selpg 將第 10 頁到第 20 頁寫至標準輸出,標準輸出被重定向至“output_file”;selpg 寫至標準錯誤的所有內容都被重定向至 /dev/null(空裝置),這意味著錯誤訊息被丟棄了。裝置檔案 /dev/null 廢棄所有寫至它的輸出,當從該裝置檔案讀取時,會立即返回 EOF。

$ selpg -s10 -e20 input_file >/dev/null

selpg 將第 10 頁到第 20 頁寫至標準輸出,標準輸出被丟棄;錯誤訊息在螢幕出現。這可作為測試 selpg 的用途,此時您也許只想(對一些測試情況)檢查錯誤訊息,而不想看到正常輸出。

$ selpg -s10 -e20 input_file | other_command

selpg 的標準輸出透明地被 shell/核心重定向,成為“other_command”的標準輸入,第 10 頁到第 20 頁被寫至該標準輸入。“other_command”的示例可以是 lp,它使輸出在系統預設印表機上列印。“other_command”的示例也可以 wc,它會顯示選定範圍的頁中包含的行數、字數和字元數。“other_command”可以是任何其它能從其標準輸入讀取的命令。錯誤訊息仍在螢幕顯示。

$ selpg -s10 -e20 input_file 2>error_file | other_command

與上面的示例 9 相似,只有一點不同:錯誤訊息被寫至“error_file”。

在以上涉及標準輸出或標準錯誤重定向的任一示例中,用“>>”替代“>”將把輸出或錯誤資料附加在目標檔案後面,而不是覆蓋目標檔案(當目標檔案存在時)或建立目標檔案(當目標檔案不存在時)。

以下所有的示例也都可以(有一個例外)結合上面顯示的重定向或管道命令。我沒有將這些特性新增到下面的示例,因為我認為它們在上面示例中的出現次數已經足夠多了。例外情況是您不能在任何包含“-dDestination”選項的 selpg 呼叫中使用輸出重定向或管道命令。實際上,您仍然可以對標準錯誤使用重定向或管道命令,但不能對標準輸出使用,因為沒有任何標準輸出 — 正在內部使用 popen() 函式由管道將它輸送至 lp 程序。

$ selpg -s10 -e20 -l66 input_file

該命令將頁長設定為 66 行,這樣 selpg 就可以把輸入當作被定界為該長度的頁那樣處理。第 10 頁到第 20 頁被寫至 selpg 的標準輸出(螢幕)。

$ selpg -s10 -e20 -f input_file

假定頁由換頁符定界。第 10 頁到第 20 頁被寫至 selpg 的標準輸出(螢幕)。

$ selpg -s10 -e20 -dlp1 input_file

第 10 頁到第 20 頁由管道輸送至命令“lp -dlp1”,該命令將使輸出在印表機 lp1 上列印。

最後一個示例將演示 Linux shell 的另一特性:

$ selpg -s10 -e20 input_file > output_file 2>error_file &

該命令利用了 Linux 的一個強大特性,即:在“後臺”執行程序的能力。在這個例子中發生的情況是:“程序標識”(pid)如 1234 將被顯示,然後 shell 提示符幾乎立刻會出現,使得您能向 shell 輸入更多命令。同時,selpg 程序在後臺執行,並且標準輸出和標準錯誤都被重定向至檔案。這樣做的好處是您可以在 selpg 執行時繼續做其它工作。

您可以通過執行命令 ps(代表“程序狀態”)檢查它是否仍在執行或已經完成。該命令會顯示數行資訊,每行代表一個從該 shell 會話啟動的程序(包括 shell 本身)。如果 selpg 仍在執行,您也將看到表示它的一項資訊。您也可以用命令“kill -l5 1234”殺死正在執行的 selpg 程序。如果這不起作用,可以嘗試用“kill -9 1234”。警告:在對任何重要程序使用該命令前,請閱讀“man kill”。

測試結果

用 Python 生成測試檔案。

f = open("test", "w+")
for i in range(500):
        f.write("line %d\n" % i)
f.close()

測試檔案test

line 0
line 1
line 2
line 3
line 4
line 5
line 6
...
ine 497
line 498
line 499

測試一:

$ ./selpg -s 1 -e 1  -l 15 test
line 0
line 1
line 2
line 3
line 4
line 5
line 6
line 7
line 8
line 9
line 10
line 11
line 12
line 13
line 14
done

測試二:

$ ./selpg -s 2 -e 3 -l 6 test
line 6
line 7
line 8
line 9
line 10
line 11
line 12
line 13
line 14
line 15
line 16
line 17
done

測試三:

$ ./selpg -s 1 -e 1 -l 10 < test
line 0
line 1
line 2
line 3
line 4
line 5
line 6
line 7
line 8
line 9
done

測試四:

$ cat test | ./selpg -s 2 -e 3 -l 10
line 10
line 11
line 12
line 13
line 14
line 15
line 16
line 17
line 18
line 19
line 20
line 21
line 22
line 23
line 24
line 25
line 26
line 27
line 28
line 29
done

測試五:

$ ./selpg -s 2 -e 3 -l 10 test > output
done

輸出檔案output

line 10
line 11
line 12
line 13
line 14
line 15
line 16
line 17
line 18
line 19
line 20
line 21
line 22
line 23
line 24
line 25
line 26
line 27
line 28
line 29

測試六:

$ ./selpg -s 2 -e 0 -l 5 test
invalid end page 0

Usage: selpg [-s start_page] [-e end_page] [ -f | -l lines_per_page ] [ -d dest ] [ in_filename ]

Options:
  -d, --d string
    	destination
  -e, --e int
    	end page (default -1)
  -f, --f
    	form-feed-delimited
  -l, --l int
    	lines per page (default 72)
  -s, --s int
    	start page (default -1)