在很多场合, 使用Go语言需要调用外部命令来完成一些特定的任务, 例如: 使用Go语言调用Linux命令来获取执行的结果,又或者调用第三方程序执行来完成额外的任务。在go的标准库中, 专门提供了os/exec包来对调用外部程序提供支持, 本文将对调用外部命令常用的几种场景进行总结。
先用Linux上的一个简单命令执行看一下效果, 执行cal命令, 会打印当前月的日期信息,如图:
如果要使用Go代码调用该命令, 可以使用以下代码:
func main(){ cmd := exec.Command("cal") err := cmd.Run() if err != nil { fmt.Println(err.Error()) }}
首先, 调用"os/exec"包中的Command函数,并传入命令名称作为参数, Command函数会返回一个exec.Cmd的命令对象。接着调用该命令对象的Run()方法运行命令。
如果此时运行程序, 会发现什么都没有出现, 这是因为我们没有处理标准输出, 调用os/exec执行命令, 标准输出和标准错误默认会被丢弃。
这里将cmd结构中的Stdout和Stderr分别设置为os.stdout和os.Stderr, 代码如下:
func main(){ cmd := exec.Command("cal") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { fmt.Println(err.Error()) }}
运行程序后显示:
输出到文件的关键, 是将exec.Cmd对象的Stdout和Stderr赋值文件句柄, 代码如下:
func main(){ f, err := os.OpenFile("sample.txt", os.O_WRONLY|os.O_CREATE, os.ModePerm) if err != nil { fmt.Println(err.Error()) } cmd := exec.Command("cal") cmd.Stdout = f cmd.Stderr = f err := cmd.Run() if err != nil { fmt.Println(err.Error()) }}
os.OpenFile打开一个文件, 指定os.0_CREATE标志让操作系统在文件不存在时自动创建, 返回文件对象*os.File, *os.File实现了io.Writer接口。
运行程序结果如下:
这里开启一个HTTP服务, 服务端接收两个参数:年和月, 在服务端通过执行系统命令返回结果,代码如下:
import ( "fmt" "net/http" "os/exec")func queryDate(w http.ResponseWriter, r *http.Request) { var err error if r.Method == "GET" { 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() if err != nil { fmt.Println(err.Error()) } }}func main() { http.HandleFunc("/querydate", queryDate) http.ListenAndServe(":8001", nil)}
打开浏览器,在地址栏中输入URL查询2023年10月份的日历:http://localhost:8001/querydate?year=2023&mnotallow=10 , 结果如下:
如果要将执行命令的结果同时输出到文件、网络和内存对象, 可以使用io.MultiWriter满足需求, io.MultiWriter可以很方便的将多个io.Writer转换成一个io.Writer, 修改之前的Web服务端程序如下:
func queryDate(w http.ResponseWriter, r *http.Request) { var err error if r.Method == "GET" { buffer := bytes.NewBuffer(nil) year := r.URL.Query().Get("year") month := r.URL.Query().Get("month") f, _ := os.OpenFile("sample.txt", os.O_WRONLY|os.O_CREATE, os.ModePerm) mw := io.MultiWriter(w, f, buffer) cmd := exec.Command("cal", month, year) cmd.Stdout = mw cmd.Stderr = mw err = cmd.Run() if err != nil { fmt.Println(err.Error()) } fmt.Println(buffer.String()) }}func main() { http.HandleFunc("/querydate", queryDate) http.ListenAndServe(":8001", nil)}
这里我们封装一个常用函数, 输入接收命令和多个参数, 返回错误和命令返回信息, 函数代码如下:
func ExecCommandOneTimeOutput(name string, args ...string) (error, string) { var out bytes.Buffer var stderr bytes.Buffer cmd := exec.Command(name, args...) cmd.Stdout = &out cmd.Stderr = &stderr err := cmd.Run() if err != nil { fmt.Println(fmt.Sprint(err) + ": " + stderr.String()) return err, "" } return nil, out.String()}
该函数可以作为通用的命令执行返回结果的函数, 分别返回了错误和命令返回信息。
在Linux系统中,有些命令运行后结果是动态持续更新的,例如: top命令,对于该场景,我们封装函数如下:
func ExecCommandLoopTimeOutput(name string, args ...string) <-chan struct{} { cmd := exec.Command(name, args...) closed := make(chan struct{}) defer close(closed) stdoutPipe, err := cmd.StdoutPipe() if err != nil { fmt.Println(err.Error()) } defer stdoutPipe.Close() go func() { scanner := bufio.NewScanner(stdoutPipe) for scanner.Scan() { fmt.Println(string(scanner.Bytes())) _, err := simplifiedchinese.GB18030.NewDecoder().Bytes(scanner.Bytes()) if err != nil { continue } } }() if err := cmd.Run(); err != nil { fmt.Println(err.Error()) } return closed}
通过调用cmd对象的StdoutPipe()输出管理函数, 我们可以实现持续获取后台命令返回的结果,并保持程序不退出。
在调用该函数的时候, 调用方式如下:
<-ExecCommandLoopTimeOutput("top")
打印出的信息将是一个持续显示信息,如图:
本章节介绍了使用os/exec这个标准库调用外部命令的各种场景。在实际应用中, 基本用的最多的还是封装好的:ExecCommandOneTimeOutput()和ExecCommandLoopTimeOutput()两个函数, 毕竟外部命令一般只会包含两种:一种是执行后马上获取结果,第二种就是常驻内存持续获取结果。
本文链接:http://www.28at.com/showinfo-26-59680-0.html在Go编程中调用外部命令的几种场景
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 全网最详细MVCC讲解,一篇看懂
下一篇: Rust真慢,差点跟同事打起来