




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第Go語言中調用外部命令的方法總結目錄引子運行命令顯示輸出顯示到標準輸出輸出到文件發(fā)送到網(wǎng)絡保存到內存對象中輸出到多個目的地運行命令,獲取輸出分別獲取標準輸出和標準錯誤標準輸入環(huán)境變量檢查命令是否存在封裝總結
引子
在工作中,我時不時地會需要在Go中調用外部命令。前段時間我做了一個工具,在釘釘群中添加了一個機器人,@這個機器人可以讓它執(zhí)行一些寫好的腳本程序完成指定的任務。機器人倒是不難,照著釘釘開發(fā)者文檔添加好機器人,然后@這個機器人就會向一個你指定的服務器發(fā)送一個POST請求,請求中會附帶文本消息。所以我要做的就是搭一個Web服務器,可以用go原生的net/http包,也可以用gin/fasthttp/fiber這些Web框架。收到請求之后,檢查附帶文本中的關鍵字去調用對應的程序,然后返回結果。
go標準庫中的os/exec包對調用外部程序提供了支持,本文詳細介紹os/exec的使用姿勢。
運行命令
Linux中有個cal命令,它可以顯示指定年、月的日歷,如果不指定年、月,默認為當前時間對應的年月。如果使用的是Windows,推薦安裝msys2,這個軟件包含了絕大多數(shù)的Linux常用命令。
那么,在Go代碼中怎么調用這個命令呢?其實也很簡單:
funcmain(){
cmd:=exec.Command("cal")
err:=cmd.Run()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
首先,我們調用exec.Command傳入命令名,創(chuàng)建一個命令對象exec.Cmd。接著調用該命令對象的Run()方法運行它。
如果你實際運行了,你會發(fā)現(xiàn)什么也沒有發(fā)生,哈哈。事實上,使用os/exec執(zhí)行命令,標準輸出和標準錯誤默認會被丟棄。
顯示輸出
exec.Cmd對象有兩個字段Stdout和Stderr,類型皆為io.Writer。我們可以將任意實現(xiàn)了io.Writer接口的類型實例賦給這兩個字段,繼而實現(xiàn)標準輸出和標準錯誤的重定向。io.Writer接口在Go標準庫和第三方庫中隨處可見,例如*os.File、*bytes.Buffer、net.Conn。所以我們可以將命令的輸出重定向到文件、內存緩存甚至發(fā)送到網(wǎng)絡中。
顯示到標準輸出
將exec.Cmd對象的Stdout和Stderr這兩個字段都設置為os.Stdout,那么輸出內容都將顯示到標準輸出:
funcmain(){
cmd:=exec.Command("cal")
cmd.Stdout=os.Stdout
cmd.Stderr=os.Stderr
err:=cmd.Run()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
運行程序。我在gitbash運行,得到如下結果:
輸出了中文,檢查一下環(huán)境變量LANG的值,果然是zh_CN.UTF-8。如果想輸出英文,可以將環(huán)境變量LANG設置為en_US.UTF-8:
$echo$LANG
zh_CN.UTF-8
$LANG=en_US.UTF-8gorunmain.go
得到輸出:
輸出到文件
打開或創(chuàng)建文件,然后將文件句柄賦給exec.Cmd對象的Stdout和Stderr這兩個字段即可實現(xiàn)輸出到文件的功能。
funcmain(){
f,err:=os.OpenFile("out.txt",os.O_WRONLY|os.O_CREATE,os.ModePerm)
iferr!=nil{
log.Fatalf("os.OpenFile()failed:%v\n",err)
cmd:=exec.Command("cal")
cmd.Stdout=f
cmd.Stderr=f
err=cmd.Run()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
os.OpenFile打開一個文件,指定os.O_CREATE標志讓操作系統(tǒng)在文件不存在時自動創(chuàng)建一個,返回該文件對象*os.File。*os.File實現(xiàn)了io.Writer接口。
運行程序:
$gorunmain.go
$catout.txt
November2025
SuMoTuWeThFrSa
12345
6789101112
13141516171819
20212223242526
27282930
發(fā)送到網(wǎng)絡
現(xiàn)在我們來編寫一個日歷服務,接收年、月信息,返回該月的日歷。
funccal(whttp.ResponseWriter,r*http.Request){
year:=r.URL.Query().Get("year")
month:=r.URL.Query().Get("month")
cmd:=exec.Command("cal",month,year)
cmd.Stdout=w
cmd.Stderr=w
err:=cmd.Run()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
funcmain(){
http.HandleFunc("/cal",cal)
http.ListenAndServe(":8080",nil)
這里為了簡單,錯誤處理都省略了。正常情況下,year和month參數(shù)都需要做合法性校驗。exec.Command函數(shù)接收一個字符串類型的可變參數(shù)作為命令的參數(shù):
funcCommand(namestring,arg...string)*Cmd
運行程序,使用瀏覽器請求localhost:8080/calyear=2025month=2得到:
保存到內存對象中
*bytes.Buffer同樣也實現(xiàn)了io.Writer接口,故如果我們創(chuàng)建一個*bytes.Buffer對象,并將其賦給exec.Cmd的Stdout和Stderr這兩個字段,那么命令執(zhí)行之后,該*bytes.Buffer對象中保存的就是命令的輸出。
funcmain(){
buf:=bytes.NewBuffer(nil)
cmd:=exec.Command("cal")
cmd.Stdout=buf
cmd.Stderr=buf
err:=cmd.Run()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
fmt.Println(buf.String())
運行:
$gorunmain.go
November2025
SuMoTuWeThFrSa
12345
6789101112
13141516171819
20212223242526
27282930
運行命令,然后得到輸出的字符串或字節(jié)切片這種模式是如此的普遍,并且使用便利,os/exec包提供了一個便捷方法:CombinedOutput。
輸出到多個目的地
有時,我們希望能輸出到文件和網(wǎng)絡,同時保存到內存對象。使用go提供的io.MultiWriter可以很容易實現(xiàn)這個需求。io.MultiWriter很方便地將多個io.Writer轉為一個io.Writer。
我們稍微修改上面的web程序:
funccal(whttp.ResponseWriter,r*http.Request){
year:=r.URL.Query().Get("year")
month:=r.URL.Query().Get("month")
f,_:=os.OpenFile("out.txt",os.O_CREATE|os.O_WRONLY,os.ModePerm)
buf:=bytes.NewBuffer(nil)
mw:=io.MultiWriter(w,f,buf)
cmd:=exec.Command("cal",month,year)
cmd.Stdout=mw
cmd.Stderr=mw
err:=cmd.Run()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
fmt.Println(buf.String())
調用io.MultiWriter將多個io.Writer整合成一個io.Writer,然后將cmd對象的Stdout和Stderr都賦值為這個io.Writer。這樣,命令運行時產(chǎn)出的輸出會分別送往http.ResponseWriter、*os.File以及*bytes.Buffer。
運行命令,獲取輸出
前面提到,我們常常需要運行命令,返回輸出。exec.Cmd對象提供了一個便捷方法:CombinedOutput()。該方法運行命令,將輸出內容以一個字節(jié)切片返回便于后續(xù)處理。所以,上面獲取輸出的程序可以簡化為:
funcmain(){
cmd:=exec.Command("cal")
output,err:=cmd.CombinedOutput()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
fmt.Println(string(output))
Soeasy!
CombinedOutput()方法的實現(xiàn)很簡單,先將標準輸出和標準錯誤重定向到*bytes.Buffer對象,然后運行程序,最后返回該對象中的字節(jié)切片:
func(c*Cmd)CombinedOutput()([]byte,error){
ifc.Stdout!=nil{
returnnil,errors.New("exec:Stdoutalreadyset")
ifc.Stderr!=nil{
returnnil,errors.New("exec:Stderralreadyset")
varbbytes.Buffer
c.Stdout=b
c.Stderr=b
err:=c.Run()
returnb.Bytes(),err
CombinedOutput方法前幾行判斷表明,Stdout和Stderr必須是未設置狀態(tài)。這其實很好理解,一般情況下,如果已經(jīng)打算使用CombinedOutput方法獲取輸出內容,不會再自找麻煩地再去設置Stdout和Stderr字段了。
與CombinedOutput類似的還有Output方法,區(qū)別是Output只會返回運行命令產(chǎn)出的標準輸出內容。
分別獲取標準輸出和標準錯誤
創(chuàng)建兩個*bytes.Buffer對象,分別賦給exec.Cmd對象的Stdout和Stderr這兩個字段,然后運行命令即可分別獲取標準輸出和標準錯誤。
funcmain(){
cmd:=exec.Command("cal","15","2012")
varstdout,stderrbytes.Buffer
cmd.Stdout=stdout
cmd.Stderr=stderr
err:=cmd.Run()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
fmt.Printf("output:\n%s\nerror:\n%s\n",stdout.String(),stderr.String())
標準輸入
exec.Cmd對象有一個類型為io.Reader的字段Stdin。命令運行時會從這個io.Reader讀取輸入。先來看一個最簡單的例子:
funcmain(){
cmd:=exec.Command("cat")
cmd.Stdin=bytes.NewBufferString("hello\nworld")
cmd.Stdout=os.Stdout
err:=cmd.Run()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
如果不帶參數(shù)運行cat命令,則進入交互模式,cat按行讀取輸入,并且原樣發(fā)送到輸出。
再來看一個復雜點的例子。Go標準庫中compress/bzip2包只提供解壓方法,并沒有壓縮方法。我們可以利用Linux命令bzip2實現(xiàn)壓縮。bzip2從標準輸入中讀取數(shù)據(jù),將其壓縮,并發(fā)送到標準輸出。
funcbzipCompress(d[]byte)([]byte,error){
varoutbytes.Buffer
cmd:=exec.Command("bzip2","-c","-9")
cmd.Stdin=bytes.NewBuffer(d)
cmd.Stdout=out
err:=cmd.Run()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
returnout.Bytes(),nil
參數(shù)-c表示壓縮,-9表示壓縮等級,9為最高。為了驗證函數(shù)的正確性,寫個簡單的程序,先壓縮helloworld字符串,然后解壓,看看是否能得到原來的字符串:
funcmain(){
data:=[]byte("helloworld")
compressed,_:=bzipCompress(data)
r:=bzip2.NewReader(bytes.NewBuffer(compressed))
decompressed,_:=ioutil.ReadAll(r)
fmt.Println(string(decompressed))
運行程序,輸出helloworld。
環(huán)境變量
環(huán)境變量可以在一定程度上微調程序的行為,當然這需要程序的支持。例如,設置ENV=production會抑制調試日志的輸出。每個環(huán)境變量都是一個鍵值對。exec.Cmd對象中有一個類型為[]string的字段Env。我們可以通過修改它來達到控制命令運行時的環(huán)境變量的目的。
packagemain
import(
"fmt"
"log"
"os"
"os/exec"
funcmain(){
cmd:=exec.Command("bash","-c","./test.sh")
nameEnv:="NAME=darjun"
ageEnv:="AGE=18"
newEnv:=append(os.Environ(),nameEnv,ageEnv)
cmd.Env=newEnv
out,err:=cmd.CombinedOutput()
iferr!=nil{
log.Fatalf("cmd.Run()failed:%v\n",err)
fmt.Println(string(out))
上面代碼獲取系統(tǒng)的環(huán)境變量,然后又添加了兩個環(huán)境變量NAME和AGE。最后使用bash運行腳本test.sh:
#!/bin/bash
echo$NAME
echo$AGE
echo$GOPATH
程序運行結果:
$gorunmain.go
darjun
18
D:\workspace\code\go
檢查命令是否存在
一般在運行命令之前,我們通過希望能檢查要運行的命令是否存在,如果存在則直接運行,否則提示用戶安裝此命令。os/exec包提供了函數(shù)LookPath可以獲取命令所在目錄,如果命令不存在,則返回一個error。
funcmain(){
path,err:=exec.LookPath("ls")
iferr!=nil{
fmt.Printf("nocmdls:%v\n",err)
}else{
fmt.Printf("findlsinpath:%s\n",path)
path,err=exec.LookPath("not-exist")
iferr!=nil{
fmt.Printf("nocmdnot-exist:%v\n",err)
}else{
fmt.Printf("findnot-existinpath:%s\n",path)
運行:
$gorunmain.go
findlsinpath:C:\ProgramFiles\Git\usr\bin\ls.exe
nocmdnot-exist:exec:not-exist:executablefilenotfoundin%PATH%
封裝
執(zhí)行外部命令的流程比較固定:
調用exec.Command()創(chuàng)建命令對象;調用Cmd.Run()執(zhí)行命令
如果要獲取輸出,需要調用CombinedOutput/Output之類的方法,或者手動創(chuàng)建bytes.Buffer對象并賦值給exec.Cmd的Stdout和Stderr字段。為了使用方便,我編寫了一個包goexec。
接口如下:
//執(zhí)行命令,丟棄標準輸出和標準錯誤
funcRunCommand(cmdstring,arg[]string,opts...Option)error
//執(zhí)行命令,以[]byte類型返回輸出
funcCombinedOutput(cmdstring,arg[]string,opts...Option)([]byte,error)
//執(zhí)行命令,以string類型返回輸出
funcCombinedOutputString(cmdstring,arg[]string,opts...Option)(string,error)
//執(zhí)行命令,以[]byte類型返回標準輸出
funcOutput(cmdstring,arg[]string,opts...Option)([]byte,error)
//執(zhí)行命令,以string類型返回標準輸出
funcOutputString(cmdstring,arg[]string,opts...Option)(string,error)
//執(zhí)行命令,以[]byte類型分別返回標準輸出和標準錯誤
funcSeparateOutput(cmdstring,arg[]string,opts...Option)([]byte,[]byte,error)
//執(zhí)行命令,以string類型分別返回標準輸出和標準錯誤
funcSeparateOutputString(cmdstring,arg[]string,opts...Option)(string,string,error)
相較于直接使用os/exec包,我傾向于一次函數(shù)調用就能獲得結果。對輸入、設置環(huán)境變量這些功能,我通過Option模式來提供支持。
t
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年臨床護理環(huán)境試題及答案分析
- 探索中國文化的試題及答案解讀
- 考試準備心理建設執(zhí)業(yè)醫(yī)師考試試題及答案
- 藥物生物等效性分析試題及答案2025
- 行政管理專業(yè)的語文備考策略與試題答案
- 成本控制風險評估
- 2025年護士實踐題目及答案總結
- 臨床決策支持系統(tǒng)的重要性試題及答案
- 精通行政事務的試題及答案
- 2025年衛(wèi)生資格考試必考內容與試題答案
- HDI盲孔的可靠性設計分析
- 2022年四川綿竹高發(fā)投資有限公司招聘筆試題庫及答案解析
- 北京中考英語詞匯表(1600詞匯)
- 籃球比賽計分表word版
- 上古卷軸5所有任務代碼
- 固體制劑批包裝記錄模版
- 2000年40個部門投入產(chǎn)出表
- 高空作業(yè)安全會議記錄內容
- 00510秘書實務-自考整合版
- 護理研究中的偏倚及控制
- 小學生的齲齒預防ppt課件
評論
0/150
提交評論