Golang應(yīng)用執(zhí)行Shell命令實(shí)戰(zhàn)
本文學(xué)習(xí)如何在Golang程序中執(zhí)行Shell命令(如,ls,mkdir或grep),如何通過(guò)stdin和stdout傳入I/O給正在運(yùn)行的命令,同時(shí)管理長(zhǎng)時(shí)間運(yùn)行的命令。為了更好的理解,針對(duì)不同場(chǎng)景由淺入深提供幾個(gè)示例進(jìn)行說(shuō)明,希望你能輕松理解。
exec包
使用官方os/exec包可以執(zhí)行外部命令,當(dāng)你執(zhí)行shell命令,是需要在Go應(yīng)用的外部運(yùn)行代碼,因此需要這些命令在子進(jìn)程中運(yùn)行。如下圖所示:
每個(gè)命令在Go應(yīng)用中作為子進(jìn)程運(yùn)行,并暴露stdin和stdout屬性,我們可以使用它們讀寫(xiě)進(jìn)程數(shù)據(jù)。
運(yùn)行基本Shell命令
運(yùn)行簡(jiǎn)單命令并從它的輸出中讀取數(shù)據(jù),通過(guò)創(chuàng)建*exec.Cmd實(shí)例實(shí)現(xiàn)。在下面示例中,使用ls列出當(dāng)前目錄下的文件,并從代碼中打印其輸出:
// create a new *Cmd instance // here we pass the command as the first argument and the arguments to pass to the command as the // remaining arguments in the function cmd := exec.Command("ls", "./") // The `Output` method executes the command and // collects the output, returning its value out, err := cmd.Output() if err != nil { // if there was any error, print it here fmt.Println("could not run command: ", err) } // otherwise, print the output from running the command fmt.Println("Output: ", string(out))
因?yàn)樵诋?dāng)前目錄下運(yùn)行程序,因此輸出項(xiàng)目根目錄下文件:
> go run shellcommands/main.go Output: ?LICENSE README.md command.go
當(dāng)運(yùn)行exec,程序沒(méi)有產(chǎn)生shell,而是直接運(yùn)行給定命令,這意味著不會(huì)進(jìn)行任何基于shell的處理,比如glob模式或擴(kuò)展。舉例,當(dāng)運(yùn)行ls ./*.md
命令,并不會(huì)如我們?cè)谀莻€(gè)shell中運(yùn)行命令一樣輸出readme.md
。
執(zhí)行長(zhǎng)時(shí)間運(yùn)行命令
前面示例執(zhí)行l(wèi)s命令立刻返回結(jié)果,但當(dāng)命令輸出是連續(xù)的、或需要很長(zhǎng)時(shí)間執(zhí)行時(shí)會(huì)怎樣呢?舉例,運(yùn)行ping命令,會(huì)周期性獲得連續(xù)結(jié)果:
ping www.baidu.com PING www.a.shifen.com (36.152.44.95) 56(84) bytes of data. 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=1 ttl=128 time=11.1 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=2 ttl=128 time=58.8 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=3 ttl=128 time=28.2 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=4 ttl=128 time=11.1 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=5 ttl=128 time=11.5 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=6 ttl=128 time=53.6 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=7 ttl=128 time=10.2 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=8 ttl=128 time=10.4 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=9 ttl=128 time=15.8 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=10 ttl=128 time=16.5 ms 64 bytes from 36.152.44.95 (36.152.44.95): icmp_seq=11 ttl=128 time=10.9 ms ^C64 bytes from 36.152.44.95: icmp_seq=12 ttl=128 time=9.92 ms
如果嘗試使用cmd.Output執(zhí)行這類(lèi)命令,則不會(huì)獲得任何結(jié)果,因?yàn)镺utput方法等待命令執(zhí)行結(jié)束,而ping無(wú)限期執(zhí)行。因此需要自定義Stdout屬性去讀取連續(xù)輸出:
cmd := exec.Command("ping", "google.com") // pipe the commands output to the applications // standard output cmd.Stdout = os.Stdout // Run still runs the command and waits for completion // but the output is instantly piped to Stdout if err := cmd.Run(); err != nil { fmt.Println("could not run command: ", err) }
再次運(yùn)行程序,輸出結(jié)果于Shell中執(zhí)行類(lèi)似。
通過(guò)直接分配Stdout屬性,我們可以在整個(gè)命令生命周期中捕獲輸出,并在接收到輸出后立即對(duì)其進(jìn)行處理。進(jìn)程間io交互如下圖所示:
自定義寫(xiě)輸出
代替使用os.Stdout,還能通過(guò)實(shí)現(xiàn)io.Writer接口創(chuàng)建自定義寫(xiě)輸出。
下面自定義代碼在每個(gè)輸出塊前增加"received output: "前綴:
type customOutput struct{} func (c customOutput) Write(p []byte) (int, error) { fmt.Println("received output: ", string(p)) return len(p), nil }
現(xiàn)在給命令輸出賦值自定義寫(xiě)輸出實(shí)例:
cmd.Stdout = customOutput{}
再次運(yùn)行程序,會(huì)獲得下面的輸出。
使用Stdin給命令傳遞輸入
前面示例沒(méi)有給命令任何輸入(或提供有限輸入作為參數(shù)),大多數(shù)場(chǎng)景中通過(guò)Stdin流傳遞輸入信息。典型的示例為grep命令,可以通過(guò)管道從一個(gè)命令串給另一個(gè)命令:
? ~ echo "1. pear\n2. grapes\n3. apple\n4. banana\n" | grep apple 3. apple
這里echo的輸出作為stdin傳給grep,輸入一組水果,通過(guò)grep過(guò)濾僅輸出apple.
*Cmd實(shí)例提供了輸入流用于寫(xiě)入,下面實(shí)例使用它傳遞輸入給grep子進(jìn)程:
cmd := exec.Command("grep", "apple") // Create a new pipe, which gives us a reader/writer pair reader, writer := io.Pipe() // assign the reader to Stdin for the command cmd.Stdin = reader // the output is printed to the console cmd.Stdout = os.Stdout go func() { defer writer.Close() // the writer is connected to the reader via the pipe // so all data written here is passed on to the commands // standard input writer.Write([]byte("1. pear\n")) writer.Write([]byte("2. grapes\n")) writer.Write([]byte("3. apple\n")) writer.Write([]byte("4. banana\n")) }() if err := cmd.Run(); err != nil { fmt.Println("could not run command: ", err) }
輸出結(jié)果:
3. apple
結(jié)束子進(jìn)程
有一些命令無(wú)限期運(yùn)行,需要能夠顯示信號(hào)去結(jié)束。舉例,如果使用python3 -m http.server
運(yùn)行web服務(wù)或sleep 10000
,則子進(jìn)程會(huì)運(yùn)行很長(zhǎng)時(shí)間或無(wú)限期運(yùn)行。
要停止進(jìn)程,需要從應(yīng)用中發(fā)送kill信號(hào),可以通過(guò)給命令增加上下文實(shí)例實(shí)現(xiàn)。如果上下文取消,則命令也會(huì)終止執(zhí)行:
ctx := context.Background() // The context now times out after 1 second // alternately, we can call `cancel()` to terminate immediately ctx, _ = context.WithTimeout(ctx, 1*time.Second) // sleep 10 second cmd := exec.CommandContext(ctx, "sleep", "10") out, err := cmd.Output() if err != nil { fmt.Println("could not run command: ", err) } fmt.Println("Output: ", string(out))
運(yùn)行程序,1秒后輸出結(jié)果:
could not run command: signal: killed
Output:
當(dāng)需要在有限時(shí)間內(nèi)運(yùn)行命令或在一定時(shí)間內(nèi)命令沒(méi)有返回結(jié)果則執(zhí)行備用邏輯。
總結(jié)
到目前為止,我們已經(jīng)學(xué)習(xí)了多種執(zhí)行unix shell命令和與之交互的方法。下面是使用os/exec包時(shí)需要注意的一些事情:
- 當(dāng)您希望執(zhí)行通常不提供太多輸出的簡(jiǎn)單命令時(shí)使用cmd.Output
- 對(duì)于具有連續(xù)或長(zhǎng)時(shí)間輸出的函數(shù)應(yīng)使用cmd.Run,并通過(guò)cmd.Stdout和cmd.Stdin與之交互
- 在生產(chǎn)場(chǎng)景中,如果進(jìn)程在給定時(shí)間內(nèi)沒(méi)有響應(yīng),須有超時(shí)并結(jié)束功能,可以使用取消上下文發(fā)送終止命令
到此這篇關(guān)于Golang應(yīng)用執(zhí)行Shell命令實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Golang執(zhí)行Shell命令內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang異常處理之defer,panic,recover的使用詳解
這篇文章主要為大家介紹了Go語(yǔ)言異常處理機(jī)制中defer、panic和recover三者的使用方法,文中示例代碼講解詳細(xì),需要的朋友可以參考下2022-05-05Golang實(shí)現(xiàn)深拷貝reflect原理示例探究
這篇文章主要為大家介紹了Golang實(shí)現(xiàn)reflect深拷貝原理示例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01GoLang調(diào)用鏈可視化go-callvis使用介紹
與鏈路追蹤(Tracing)不同,Tracing關(guān)注復(fù)雜的分布式環(huán)境中各個(gè)服務(wù)節(jié)點(diǎn)間的調(diào)用關(guān)系,主要用于服務(wù)治理。而我們本次探索的代碼調(diào)用鏈路則是代碼方法級(jí)別的調(diào)用關(guān)系,主要用于代碼設(shè)計(jì)2023-02-02golang如何實(shí)現(xiàn)mapreduce單進(jìn)程版本詳解
這篇文章主要給大家介紹了關(guān)于golang如何實(shí)現(xiàn)mapreduce單進(jìn)程版本的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01go語(yǔ)言通過(guò)zlib壓縮數(shù)據(jù)的方法
這篇文章主要介紹了go語(yǔ)言通過(guò)zlib壓縮數(shù)據(jù)的方法,實(shí)例分析了Go語(yǔ)言中zlib的使用技巧,需要的朋友可以參考下2015-03-03Golang 語(yǔ)言map底層實(shí)現(xiàn)原理解析
這篇文章主要介紹了Golang 語(yǔ)言map底層實(shí)現(xiàn)原理解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12