基於Golang的CLI 命令列程式開發
基於Golang的CLI 命令列程式開發 【閱讀時間:約15分鐘】
- 一. CLI 命令列程式概述
- 二. 系統環境&專案介紹&開發準備
- 三.具體程式設計及Golang程式碼實現
- 1.selpg的程式結構
- 2.匯入的庫
- 3.sp_args結構體
- 4.全域性變數
- 4.main函式
- 5.process_args函式
- 6.process_args函式
- 7.usage函式
- 四.程式測試
- 1.單元測試
- 2.功能測試
- (1)`selpg -s1 -e1 in.txt`
- (2)`selpg -s1 -e1 < in.txt`
- (3)`other_command | selpg -s1 -e1`
- (4)`selpg -s1 -e1 in.txt >out.txt`
- (5)`selpg -s20 -e20 in.txt 2>error.txt`
- (6)`selpg -s1 -e1 in.txt >out.txt 2>error.txt`
- (7)`selpg -s20 -e20 in.txt >out.txt 2>/dev/null`
- (8)`selpg -s10 -e20 in.txt >/dev/null`
- (9)`selpg -s10 -e20 input_file 2>error_file | other_command`
- (10)`selpg -s10 -e20 input_file 2>error_file | other_command`
- (11)`selpg -s1 -e1 -l10 in.txt`
- (12)`selpg -s1 -e1 -f in.txt`
- (13)`selpg -s1 -e1 in.txt | cat -n`
- (14)`selpg -s10 -e20 in.txt > out.txt 2>error.txt &`
- 五.總結
- 六. References
一. CLI 命令列程式概述
CLI(Command Line Interface)實用程式是Linux下應用開發的基礎。正確的編寫命令列程式讓應用與作業系統融為一體,通過shell或script使得應用獲得最大的靈活性與開發效率。例如:
Linux提供了cat、ls、copy等命令與作業系統互動;
go語言提供一組實用程式完成從編碼、編譯、庫管理、產品釋出全過程支援;
容器服務如docker、k8s提供了大量實用程式支撐雲服務的開發、部署、監控、訪問等管理任務;
git、npm等也是大家比較熟悉的工具。
儘管作業系統與應用系統服務視覺化、圖形化,但在開發領域,CLI在程式設計、除錯、運維、管理中提供了圖形化程式不可替代的靈活性與效率。
二. 系統環境&專案介紹&開發準備
1.系統環境
作業系統:CentOS7
硬體資訊:使用virtual box配置虛擬機器(記憶體3G、磁碟30G)
程式語言:GO 1.15.2
2.專案介紹
本專案的開發主要基於IBM Developer社群的C語言程式(https://www.ibm.com/developerworks/cn/linux/shell/clutil/index.html),出於熟悉golang語言的目的,筆者主要的工作只是將其翻譯為golang格式,其中還使用了部分庫,如os和pflag,再次感謝原作者及開原始碼工作者。
專案完成後的執行效果與CLI 命令列程式一致,一個簡單的輸出文字第一頁20行的內容的例子如下:
3.開發準備
①首先下載上文的C語言原始碼(點選下載)
②安裝並使用 pflag 替代 goflag 以滿足 Unix 命令列規範,此處出於篇幅考慮,只在後面的函式介紹時給出部分使用教程,詳細的pflag 使用教程可見【六. References. 1. Golang之使用Flag和Pflag】
③將C語言原始碼翻譯為golang語言
三.具體程式設計及Golang程式碼實現
1.selpg的程式結構
selpg的程式結構非常簡單,主要有以下組成:
①sp_args結構
②main函式
③process_args函式
④process_input函式
⑤usage函式
2.匯入的庫
主要要匯入的庫有:
①bufio:用於檔案的讀寫
②io:用於檔案讀寫、讀環境變數
③pflag:用於解釋命令列引數,替代 goflag 以滿足 Unix 命令列規範
/*================================= includes ======================*/
package main
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"github.com/spf13/pflag"
)
3.sp_args結構體
sp_args結構體是用於記錄資料的結構體,分別記錄著開始頁碼,結束頁碼,檔名,每頁大小,頁的型別和列印輸出位置等資訊。
/*================================= types =========================*/
type sp_args struct {
start_page int
end_page int
in_filename string
page_len int /* default value, can be overriden by "-l number" on command line */
page_type bool /* 'l' for lines-delimited, 'f' for form-feed-delimited */
/* default is 'l' */
print_dest string
}
4.全域性變數
全域性變數共有兩個:
①progname是程式名,在輸出錯誤資訊時有用;
②用 INT_MAX 檢查一個數是否為有效整數,由於golang沒有預定義的INT_MAX,此處用別的方式來手動實現
/*================================= globals =======================*/
var progname string /* program name, for error messages */
const INT_MAX = int(^uint(0) >> 1) //golang需要手動宣告INT_MAX
4.main函式
main函式作為程式的入口,給出了整個程式的大概執行過程。
①首先進行sp_args變數和progname的初始化,其中主要的預設屬性為開始頁碼和結束頁碼均為1,每頁長度為20行,不可用用換頁符換頁
②然後呼叫process_args函式來處理輸入時的各種引數錯誤
③最後才呼叫process_input函式來執行輸入的引數。
/*================================= main()=== =====================*/
func main() {
var sa sp_args
sa.start_page = 1
sa.end_page = 1
sa.in_filename = ""
sa.page_len = 20 //預設20行一頁
sa.page_type = false
sa.print_dest = ""
/* save name by which program is invoked, for error messages */
progname = os.Args[0]
process_args(len(os.Args), &sa)
process_input(sa)
}
5.process_args函式
process_args函式用於處理輸入時的各種引數錯誤。
①首先通過pflag繫結各引數和usage函式
②然後判斷各種引數的錯誤即可,比如起始頁碼是負數,終止頁碼小於起始頁碼等情況,具體的錯誤情況在程式碼中已給出註釋
③當發生錯誤,首先通過pflag.usage函式輸出正確的指令引數格式來提醒使用者,並通過os.Exit函式退出程式
/*================================= process_args() ================*/
func process_args(ac int, psa *sp_args) {
//指令格式:selpg -sstart_page -eend_page [-lline | -f ] [-d dstFile] filename
//使用pflag繫結各引數, psa初始化
pflag.Usage = usage
pflag.IntVarP(&psa.start_page, "start_page", "s", 1, "Start page")
pflag.IntVarP(&psa.end_page, "end_page", "e", 1, "End page")
pflag.IntVarP(&psa.page_len, "page_len", "l", 20, "Lines per page")
pflag.BoolVarP(&psa.page_type, "page_type", "f", false, "Page type")
pflag.StringVarP(&psa.print_dest, "dest", "d", "", "Destination")
pflag.Parse()
/* check the command-line arguments for validity */
if ac < 3 { /* Not enough args, minimum command is "selpg -sstartpage -eend_page" */
fmt.Fprintf(os.Stderr, "%s: not enough arguments\n", progname)
pflag.Usage()
os.Exit(1)
}
/* handle 1st arg - start page */
temp := os.Args[1]
if temp[0:2] != "-s" {
fmt.Fprintf(os.Stderr, "%s: 1st arg should be -sstart_page\n", progname)
pflag.Usage()
os.Exit(2)
}
if psa.start_page < 1 || psa.start_page > (INT_MAX-1) {
fmt.Fprintf(os.Stderr, "%s: invalid start page %d\n", progname, psa.start_page)
pflag.Usage()
os.Exit(3)
}
/* handle 2nd arg - end page */
temp = os.Args[2]
if temp[0:2] != "-e" {
fmt.Fprintf(os.Stderr, "%s: 2nd arg should be -eend_page\n", progname)
pflag.Usage()
os.Exit(4)
}
if psa.end_page < 1 || psa.end_page > (INT_MAX-1) || psa.end_page < psa.start_page {
fmt.Fprintf(os.Stderr, "%s: invalid end page %d\n", progname, psa.end_page)
pflag.Usage()
os.Exit(5)
}
/* now handle optional args */
//使用pflag,selpg.c的while+switch可去掉
if psa.page_len != 5 {
if psa.page_len < 1 {
fmt.Fprintf(os.Stderr, "%s: invalid page length %d\n", progname, psa.page_len)
pflag.Usage()
os.Exit(6)
}
}
if pflag.NArg() > 0 { /* there is one more arg */
psa.in_filename = pflag.Arg(0)
/* check if file exists */
file, err := os.Open(psa.in_filename)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: input file \"%s\" does not exist\n", progname, psa.in_filename)
os.Exit(7)
}
/* check if file is readable */
file, err = os.OpenFile(psa.in_filename, os.O_RDONLY, 0666)
if err != nil {
if os.IsPermission(err) {
fmt.Fprintf(os.Stderr, "%s: input file \"%s\" exists but cannot be read\n", progname, psa.in_filename)
os.Exit(8)
}
}
file.Close()
}
}
6.process_args函式
process_input函式用於執行輸入的引數,執行檔案讀寫和輸出到螢幕等操作。其中由於沒有印表機,轉而使用cat命令測試。
/*================================= process_input() ===============*/
func process_input(sa sp_args) {
var fin *os.File /* input stream */
var fout io.WriteCloser /* output stream */
var c byte /* to read 1 char */
var line string
var line_ctr int /* line counter */
var page_ctr int /* page counter */
var err error
cmd := &exec.Cmd{}
/* set the input source */
if sa.in_filename == "" {
fin = os.Stdin
} else {
fin, err = os.Open(sa.in_filename)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: could not open input file \"%s\"\n", progname, sa.in_filename)
os.Exit(9)
}
}
/* set the output destination */
if sa.print_dest == "" {
fout = os.Stdout
} else {
cmd = exec.Command("cat") //由於沒有印表機,使用cat命令測試
cmd.Stdout, err = os.OpenFile(sa.print_dest, os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: could not open output file \"%s\"\n", progname, sa.print_dest)
os.Exit(10)
}
fout, err = cmd.StdinPipe()
if err != nil {
fmt.Fprintf(os.Stderr, "%s: could not open pipe to \"%s\"\n", progname, sa.print_dest)
os.Exit(11)
}
cmd.Start()
}
/* begin one of two main loops based on page type */
rd := bufio.NewReader(fin)
if sa.page_type == false {
line_ctr = 0
page_ctr = 1
for true {
line, err = rd.ReadString('\n')
if err != nil { /* error or EOF */
break
}
line_ctr++
if line_ctr > sa.page_len {
page_ctr++
line_ctr = 1
}
if page_ctr >= sa.start_page && page_ctr <= sa.end_page {
fmt.Fprintf(fout, "%s", line)
}
}
} else {
page_ctr = 1
for true {
c, err = rd.ReadByte()
if err != nil { /* error or EOF */
break
}
if c == '\f' {
page_ctr++
}
if page_ctr >= sa.start_page && page_ctr <= sa.end_page {
fmt.Fprintf(fout, "%c", c)
}
}
fmt.Print("\n")
}
/* end main loop */
if page_ctr < sa.start_page {
fmt.Fprintf(os.Stderr, "%s: start_page (%d) greater than total pages (%d), no output written\n", progname, sa.start_page, page_ctr)
} else if page_ctr < sa.end_page {
fmt.Fprintf(os.Stderr, "%s: end_page (%d) greater than total pages (%d), less output than expected\n", progname, sa.end_page, page_ctr)
}
fin.Close()
fout.Close()
fmt.Fprintf(os.Stderr, "%s: done\n", progname)
}
7.usage函式
usage函式用於輸出正確的指令引數格式。
/*================================= usage() =======================*/
func usage() {
fmt.Fprintf(os.Stderr, "\nUSAGE: %s -sstart_page -eend_page [ -f | -llines_per_page ] [ -ddest ] [ in_filename ]\n", progname)
}
四.程式測試
1.單元測試
2.功能測試
此處按照IBM的c語言程式的使用例項來進行功能測試。
首先在selpg目錄下建立三個txt檔案,分別為:
①in.txt, 用於輸入的文字,內容如下(為方便演示,只有20行):
②out.txt, 儲存輸出的文字,內容初始為空
③error.txt,儲存錯誤資訊,內容初始為空
(1)selpg -s1 -e1 in.txt
該命令將把“in.txt”的第 1 頁寫至標準輸出(也就是螢幕),因為這裡沒有重定向或管道。
[henryhzy@localhost selpg]$ selpg -s1 -e1 in.txt
Hello world!
I am HenryHZY.
line 1
iine 2
line 3
line 4
line 5
line 6
iine 7
line 8
line 9
line 10
line 11
iine 12
line 13
line 14
line 15
line 16
iine 17
line 18
selpg: done
(2)selpg -s1 -e1 < in.txt
該命令與示例 1 所做的工作相同,但在本例中,selpg 讀取標準輸入,而標準輸入已被 shell/核心重定向為來自“in.txt”而不是顯式命名的檔名引數。輸入的第 1 頁被寫至螢幕。
[henryhzy@localhost selpg]$ selpg -s1 -e1 < in.txt
Hello world!
I am HenryHZY.
line 1
iine 2
line 3
line 4
line 5
line 6
iine 7
line 8
line 9
line 10
line 11
iine 12
line 13
line 14
line 15
line 16
iine 17
line 18
selpg: done
(3)other_command | selpg -s1 -e1
“other_command”的標準輸出被 shell/核心重定向至 selpg 的標準輸入。將第 1頁寫至 selpg 的標準輸出(螢幕)。
[henryhzy@localhost selpg]$ ls | selpg -s1 -e1
error.txt
in.txt
out.txt
selpg.go
selpg: done
(4)selpg -s1 -e1 in.txt >out.txt
selpg 將第 1 頁寫至標準輸出;標準輸出被 shell/核心重定向至out.txt“”。
(5)selpg -s20 -e20 in.txt 2>error.txt
selpg 將第 20 頁至標準輸出(螢幕);所有的錯誤訊息被 shell/核心重定向至“error.txt”。請注意:在“2”和“>”之間不能有空格;這是 shell 語法的一部分(請參閱“man bash”或“man sh”)。
(6)selpg -s1 -e1 in.txt >out.txt 2>error.txt
selpg 將第 1頁寫至標準輸出,標準輸出被重定向至“output_file”;selpg 寫至標準錯誤的所有內容都被重定向至“error_file”。當“input_file”很大時可使用這種呼叫;您不會想坐在那裡等著 selpg 完成工作,並且您希望對輸出和錯誤都進行儲存。
(7)selpg -s20 -e20 in.txt >out.txt 2>/dev/null
selpg 將第 20 頁寫至標準輸出,標準輸出被重定向至“output_file”;selpg 寫至標準錯誤的所有內容都被重定向至 /dev/null(空裝置),這意味著錯誤訊息被丟棄了。裝置檔案 /dev/null 廢棄所有寫至它的輸出,當從該裝置檔案讀取時,會立即返回 EOF。
此處本應有的的error資訊被丟棄了。
(8)selpg -s10 -e20 in.txt >/dev/null
selpg 將第 10 頁到第 20 頁寫至標準輸出,標準輸出被丟棄;錯誤訊息在螢幕出現。這可作為測試 selpg 的用途,此時您也許只想(對一些測試情況)檢查錯誤訊息,而不想看到正常輸出。
[henryhzy@localhost selpg]$ selpg -s10 -e20 in.txt >/dev/null
selpg: start_page (10) greater than total pages (1), no output written
selpg: done
(9)selpg -s10 -e20 input_file 2>error_file | other_command
selpg 的標準輸出透明地被 shell/核心重定向,成為“other_command”的標準輸入,第 1頁被寫至該標準輸入。“other_command”的示例可以是 lp,它使輸出在系統預設印表機上列印。“other_command”的示例也可以 wc,它會顯示選定範圍的頁中包含的行數、字數和字元數。“other_command”可以是任何其它能從其標準輸入讀取的命令。錯誤訊息仍在螢幕顯示。
[henryhzy@localhost selpg]$ selpg -s1 -e1 in.txt | ps
selpg: done
PID TTY TIME CMD
10209 pts/0 00:00:00 bash
10528 pts/0 00:00:00 ps
(10)selpg -s10 -e20 input_file 2>error_file | other_command
與上面的示例 9 相似,只有一點不同:錯誤訊息被寫至“error_file”。
(11)selpg -s1 -e1 -l10 in.txt
該命令將頁長設定為 10 行,這樣 selpg 就可以把輸入當作被定界為該長度的頁那樣處理。文字的前10行被寫至 selpg 的標準輸出(螢幕)。
[henryhzy@localhost selpg]$ selpg -s1 -e1 -l10 in.txt
Hello world!
I am HenryHZY.
line 1
iine 2
line 3
line 4
line 5
line 6
iine 7
line 8
selpg: done
(12)selpg -s1 -e1 -f in.txt
假定頁由換頁符定界。第 10頁被寫至 selpg 的標準輸出(螢幕)。
[henryhzy@localhost selpg]$ selpg -s1 -e1 -f in.txt
Hello world!
I am HenryHZY.
line 1
iine 2
line 3
line 4
line 5
line 6
iine 7
line 8
line 9
line 10
line 11
iine 12
line 13
line 14
line 15
line 16
iine 17
line 18
selpg: done
(13)selpg -s1 -e1 in.txt | cat -n
由於沒有印表機,原測試的印表機輸出改為cat輸出。
[henryhzy@localhost selpg]$ selpg -s1 -e1 in.txt | cat -n
selpg: done
1 Hello world!
2 I am HenryHZY.
3 line 1
4 iine 2
5 line 3
6 line 4
7 line 5
8 line 6
9 iine 7
10 line 8
11 line 9
12 line 10
13 line 11
14 iine 12
15 line 13
16 line 14
17 line 15
18 line 16
19 iine 17
20 line 18
(14)selpg -s10 -e20 in.txt > out.txt 2>error.txt &
該命令利用了 Linux 的一個強大特性,即:在“後臺”執行程式的能力。在這個例子中發生的情況是:“程式標識”(pid)如 1234 將被顯示,然後 shell 提示符幾乎立刻會出現,使得您能向 shell 輸入更多命令。同時,selpg 程式在後臺執行,並且標準輸出和標準錯誤都被重定向至檔案。這樣做的好處是您可以在 selpg 執行時繼續做其它工作。
五.總結
具體程式碼可見gitee
六. References
相關文章
- 基於node和npm的命令列工具——tive-cliNPM命令列
- golang常用庫:cli命令列/應用程式生成工具-cobra使用Golang命令列
- 基於 Laravel 命令列開發 API 程式碼生成器Laravel命令列API
- 基於命令列的WINCE驅動開發工具命令列
- 基於gin的golang web開發:路由GolangWeb路由
- 基於gin的golang web開發:dockerGolangWebDocker
- 基於gin的golang web開發:路由二GolangWeb路由
- 基於 golang + vue + websocket 開發的聊天室GolangVueWeb
- 基於gin的golang web開發:中介軟體GolangWeb
- 基於gin的golang web開發:模型繫結GolangWeb模型
- 基於gin的golang web開發:模型驗證GolangWeb模型
- [hive] hive cli 命令列Hive命令列
- 服務計算--使用 golang 開發 開發 Linux 命令列實用程式 中的 selpg作業3GolangLinux命令列
- 基於gin的golang web開發:服務間呼叫GolangWeb
- 基於gin的golang web開發:整合swaggerGolangWebSwagger
- 基於gin的golang web開發:認證利器jwtGolangWebJWT
- 基於gin的golang web開發:Gin技術拾遺GolangWeb
- 基於gin的golang web開發:mysql增刪改查GolangWebMySql
- Node.js 命令列程式開發教程Node.js命令列
- 基於命令列的工作管理員 Taskwarrior命令列
- Vue-cli 命令列工具分析Vue命令列
- node.js 命令列工具(cli)Node.js命令列
- 命令列分析元件IKende.CLI命令列元件
- vue專案通過命令列傳參實現多環境配置(基於@vue/cli)Vue命令列
- 基於gin的golang web開發:使用資料庫事務GolangWeb資料庫
- 基於gin的golang web開發:訪問mysql資料庫GolangWebMySql資料庫
- node命令列開發命令列
- 火影推薦程式連載59-基於gin的golang web開發:模型驗證GolangWeb模型
- 在命令列開發 Android 應用程式命令列Android
- 開發 Linux 命令列實用程式(轉)Linux命令列
- Oracle EBS 基於Host併發程式的開發Oracle
- ThinkPHP 使用命令列 (cli) think 呼叫PHP命令列
- 基於 Golang 開發的分散式定時任務管理系統Golang分散式
- 基於gin的golang web開發:實現使用者登入GolangWeb
- Odin —— 用於構建命令列應用的 Go 開發包命令列Go
- 基於 ChatGpt Api 開發的微信小程式ChatGPTAPI微信小程式
- nodejs模組釋出及命令列程式開發NodeJS命令列
- 基於RDD的Spark應用程式開發案列講解(詞頻統計)Spark