• 在Go编程中调用外部命令的几种场景


    1.摘要

    在很多场合, 使用Go语言需要调用外部命令来完成一些特定的任务, 例如: 使用Go语言调用Linux命令来获取执行的结果,又或者调用第三方程序执行来完成额外的任务。在go的标准库中, 专门提供了os/exec包来对调用外部程序提供支持, 本文将对调用外部命令的几种使用方法进行总结。

    2.直接调用函数

    先用Linux上的一个简单命令执行看一下效果, 执行cal命令, 会打印当前月的日期信息,如图:

    如果要使用Go代码调用该命令, 可以使用以下代码:

    1. func main(){
    2. cmd := exec.Command("cal")
    3. err := cmd.Run()
    4. if err != nil {
    5. fmt.Println(err.Error())
    6. }
    7. }

    首先, 调用"os/exec"包中的Command函数,并传入命令名称作为参数, Command函数会返回一个exec.Cmd的命令对象。接着调用该命令对象的Run()方法运行命令。

    如果此时运行程序, 会发现什么都没有出现, 这是因为我们没有处理标准输出, 调用os/exec执行命令, 标准输出和标准错误默认会被丢弃。

    这里将cmd结构中的Stdout和Stderr分别设置为os.stdout和os.Stderr, 代码如下:

    1. func main(){
    2. cmd := exec.Command("cal")
    3. cmd.Stdout = os.Stdout
    4. cmd.Stderr = os.Stderr
    5. err := cmd.Run()
    6. if err != nil {
    7. fmt.Println(err.Error())
    8. }
    9. }

    运行程序后显示:

    3.输出到文件

    输出到文件的关键, 是将exec.Cmd对象的Stdout和Stderr赋值文件句柄, 代码如下:

    1. func main(){
    2.   f, err := os.OpenFile("sample.txt", os.O_WRONLY|os.O_CREATE, os.ModePerm)
    3.   if err != nil {
    4. fmt.Println(err.Error())
    5. }
    6. cmd := exec.Command("cal")
    7. cmd.Stdout = f
    8. cmd.Stderr = f
    9. err := cmd.Run()
    10. if err != nil {
    11. fmt.Println(err.Error())
    12. }
    13. }

    os.OpenFile打开一个文件, 指定os.0_CREATE标志让操作系统在文件不存在时自动创建, 返回文件对象*os.File, *os.File实现了io.Writer接口。

    运行程序结果如下:

    4.发送到网络

    这里开启一个HTTP服务, 服务端接收两个参数:年和月, 在服务端通过执行系统命令返回结果,代码如下:

    1. import (
    2. "fmt"
    3. "net/http"
    4. "os/exec"
    5. )
    6. func queryDate(w http.ResponseWriter, r *http.Request) {
    7. var err error
    8. if r.Method == "GET" {
    9. year := r.URL.Query().Get("year")
    10. month := r.URL.Query().Get("month")
    11. cmd := exec.Command("cal", month, year)
    12. cmd.Stdout = w
    13. cmd.Stderr = w
    14. err = cmd.Run()
    15. if err != nil {
    16. fmt.Println(err.Error())
    17. }
    18. }
    19. }
    20. func main() {
    21. http.HandleFunc("/querydate", queryDate)
    22. http.ListenAndServe(":8001", nil)
    23. }

    打开浏览器,在地址栏中输入URL查询2023年10月份的日历:

    http://localhost:8001/querydate?year=2023&month=10 , 结果如下:

    5.输出到多个目标

    如果要将执行命令的结果同时输出到文件、网络和内存对象, 可以使用io.MultiWriter满足需求, io.MultiWriter可以很方便的将多个io.Writer转换成一个io.Writer, 修改之前的Web服务端程序如下:

    1. func queryDate(w http.ResponseWriter, r *http.Request) {
    2. var err error
    3. if r.Method == "GET" {
    4. buffer := bytes.NewBuffer(nil)
    5. year := r.URL.Query().Get("year")
    6. month := r.URL.Query().Get("month")
    7. f, _ := os.OpenFile("sample.txt", os.O_WRONLY|os.O_CREATE, os.ModePerm)
    8. mw := io.MultiWriter(w, f, buffer)
    9. cmd := exec.Command("cal", month, year)
    10. cmd.Stdout = mw
    11. cmd.Stderr = mw
    12. err = cmd.Run()
    13. if err != nil {
    14. fmt.Println(err.Error())
    15. }
    16. fmt.Println(buffer.String())
    17. }
    18. }
    19. func main() {
    20. http.HandleFunc("/querydate", queryDate)
    21. http.ListenAndServe(":8001", nil)
    22. }

    6.分别获取输出内容和错误

    这里我们封装一个常用函数, 输入接收命令和多个参数, 返回错误和命令返回信息, 函数代码如下:

    1. func ExecCommandOneTimeOutput(name string, args ...string) (error, string) {
    2. var out bytes.Buffer
    3. var stderr bytes.Buffer
    4. cmd := exec.Command(name, args...)
    5. cmd.Stdout = &out
    6. cmd.Stderr = &stderr
    7. err := cmd.Run()
    8. if err != nil {
    9. fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
    10. return err, ""
    11. }
    12. return nil, out.String()
    13. }

    该函数可以作为通用的命令执行返回结果的函数, 分别返回了错误和命令返回信息。

    7.循环获取命令内容

    在Linux系统中,有些命令运行后结果是动态持续更新的,例如: top命令,对于该场景,我们封装函数如下:

    1. func ExecCommandLoopTimeOutput(name string, args ...string) <-chan struct{} {
    2. cmd := exec.Command(name, args...)
    3. closed := make(chan struct{})
    4. defer close(closed)
    5. stdoutPipe, err := cmd.StdoutPipe()
    6. if err != nil {
    7. fmt.Println(err.Error())
    8. }
    9. defer stdoutPipe.Close()
    10. go func() {
    11. scanner := bufio.NewScanner(stdoutPipe)
    12. for scanner.Scan() {
    13. fmt.Println(string(scanner.Bytes()))
    14. _, err := simplifiedchinese.GB18030.NewDecoder().Bytes(scanner.Bytes())
    15. if err != nil {
    16. continue
    17. }
    18. }
    19. }()
    20. if err := cmd.Run(); err != nil {
    21. fmt.Println(err.Error())
    22. }
    23. return closed
    24. }

    通过调用cmd对象的StdoutPipe()输出管理函数, 我们可以实现持续获取后台命令返回的结果,并保持程序不退出。

    在调用该函数的时候, 调用方式如下:

    <-ExecCommandLoopTimeOutput("top")

    打印出的信息将是一个持续显示信息,如图:

    8.总结

    本章节介绍了使用os/exec这个标准库调用外部命令的各种场景。在实际应用中, 基本用的最多的还是封装好的:ExecCommandOneTimeOutput()和ExecCommandLoopTimeOutput()两个函数, 毕竟外部命令一般只会包含两种:一种是执行后马上获取结果,第二种就是常驻内存持续获取结果。

  • 相关阅读:
    Java基础(第七期):Java面向对象和类 && 类的封装 &&Java构造器 && JavaBean标准
    Python实现简易过滤删除数字的方法
    SAP 创建采购订单报错:未在项目项目号 公司代码 科目号 中输入承诺项目
    漫谈:C语言 C++ 迷惑的语句、分号、大括号
    [翻译] Cassandra 分布式结构化存储系统
    序列解包和生成器表达式
    java使用poi-tl模版引擎导出word之列表循环数据渲染
    在线教育项目【统一异常处理】
    劳动工资电子台账操作流程
    面试打底稿④ 专业技能的第四部分
  • 原文地址:https://blog.csdn.net/suntiger/article/details/134483527