TrumanWong

Golang实时读取命令行输出

TrumanWong
6/14/2023

使用golang调用阻塞式shell命令(如pingtail -f等),按普通的调用方法是无法拿到实时输出结果的,这里可以通过异步读取管道输出数据的方式实现,关键代码如下:

// command.go
package command

import (
    "bufio"
    "io"
    "log"
    "os/exec"
    "runtime/debug"
    "sync"
)

func readLine(wg *sync.WaitGroup, out chan string, reader io.ReadCloser) {
    defer func() {
        if r := recover(); r != nil {
            log.Println(r, string(debug.Stack()))
        }
    }()
    defer wg.Done()
    r := bufio.NewReader(reader)
    for {
        line, _, err := r.ReadLine()
        if err == io.EOF || err != nil {
            return
        }
        out <- string(line)
    }
}

// ExecCommandRealTimeOutput 读取命令行实时输出
// Exec command and read real time output
func ExecCommandRealTimeOutput(out chan string, name string, arg ...string) error {
    cmd := exec.Command(name, arg...)
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    if err := cmd.Start(); err != nil {
        return err
    }
    wg := sync.WaitGroup{}
    defer wg.Wait()
    wg.Add(2)
    go readLine(&wg, out, stdout)
    go readLine(&wg, out, stderr)
    if err := cmd.Wait(); err != nil {
        return err
    }
    return nil
}

测试用例:

// command_test.go
package command

import (
    "log"
    "testing"
)

func TestExecCommandRealTimeOutput(t *testing.T) {
    out := make(chan string)
    defer close(out)
    go func() {
        for {
            str, ok := <-out
            if !ok {
                break
            }
            log.Println(str)
        }
    }()
    args := []string{"-c", "ping www.trumanwl.com"}
    if err := ExecCommandRealTimeOutput(out, "bash", args...); err != nil {
        t.Error(err)
    }
}

测试:

$ go test -v command_test.go command.go 
=== RUN   TestExecCommandRealTimeOutput
2023/06/14 14:54:51 PING cname.vercel-dns.com (76.76.21.22) 56(84) bytes of data.
2023/06/14 14:54:51 64 bytes from 76.76.21.22 (76.76.21.22): icmp_seq=1 ttl=128 time=55.7 ms
2023/06/14 14:54:52 64 bytes from 76.76.21.22 (76.76.21.22): icmp_seq=2 ttl=128 time=74.6 ms
2023/06/14 14:54:53 64 bytes from 76.76.21.22 (76.76.21.22): icmp_seq=3 ttl=128 time=63.1 ms
2023/06/14 14:54:54 64 bytes from 76.76.21.22 (76.76.21.22): icmp_seq=4 ttl=128 time=63.8 ms
2023/06/14 14:54:55 64 bytes from 76.76.21.22 (76.76.21.22): icmp_seq=5 ttl=128 time=56.6 ms

文中代码已推送至github