获取 os.exec 的输出以显示执行命令的进度

Posted

技术标签:

【中文标题】获取 os.exec 的输出以显示执行命令的进度【英文标题】:Get output of os.exec to to show progress executed command 【发布时间】:2021-01-08 08:32:30 【问题描述】:

我有一个类似下面的函数来执行命令并将输出显示到标准输出(终端):

package main

// https://blog.kowalczyk.info/article/wOYk/advanced-command-execution-in-go-with-osexec.html

import (
    "bytes"
    "io"
    "fmt"
    "log"
    "os"
    "os/exec"
)

func main() 
    cmd := exec.Command("./test.sh")

    var stdoutBuf, stderrBuf bytes.Buffer
    cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
    cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)

    err := cmd.Run()
    if err != nil 
        log.Fatalf("cmd.Run() failed with %s\n", err)
    
    outStr, errStr := string(stdoutBuf.Bytes()), string(stderrBuf.Bytes())
    fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr)


这是要测试的 bash 脚本:

#!/bin/bash

declare -a arr=("frontend" "backend" "middleware" "apigateway")
for service in "$arr[@]"
do
  sleep 1
  echo $service-prod-secret.yaml
done

目前,我只有在命令运行打印输出到 os.Stdout(终端)时的输出,但我想逐行获取此输出并推送到某个位置(EX:套接字)以显示进度执行命令,因为我有一个命令需要很长时间才能完成,所以很难只看到白屏而不知道命令的进度。

输出:

frontend-prod-secret.yaml
backend-prod-secret.yaml
middleware-prod-secret.yaml
apigateway-prod-secret.yaml

out:
frontend-prod-secret.yaml
backend-prod-secret.yaml
middleware-prod-secret.yaml
apigateway-prod-secret.yaml

我需要等待命令完成才能获得输出,但这需要很长时间。

如何逐行(实时)引用输出以推送到我想要的位置?

【问题讨论】:

您希望用户在等待命令执行时看到命令的输出还是看到其他内容而不是白屏? 是的,@whitespace 我想将stdout复制到某个地方,但是我只能在命令完成后才能得到输出,这需要很长时间 您可以使用 go 例程在缓冲区可用时立即获取数据。 @ChenA。我的程序运行良好,你能给我一个简单的解决方案吗?我也这么认为,但没能实现。 【参考方案1】:

您应该监听 stdout 和 stderr 缓冲区,并在它们获得新数据时打印它们。

您可以使用 Scanner 扫描缓冲区:

package main

import (
    "bufio"
    "fmt"
    "io"
    "os/exec"
)

func main() 
    cmd := exec.Command("test.sh")
    
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    _ = cmd.Start()

    scanner := bufio.NewScanner(io.MultiReader(stdout, stderr))
    scanner.Split(bufio.ScanWords)
    for scanner.Scan() 
        m := scanner.Text()
        fmt.Println(m)
    
    _ = cmd.Wait()


打印出现在缓冲区中的数据(因此行之间存在睡眠)

frontend-prod-secret.yaml
backend-prod-secret.yaml
middleware-prod-secret.yaml
apigateway-prod-secret.yaml

【讨论】:

【参考方案2】:

os.StdOut 正在寻找具有Write 方法的接口,在写入其输出时它将调用此方法。

我想逐行获取这个输出并推送到某个地方(例如:socket)

我举个例子

import (
    "flag"
    "github.com/gorilla/websocket"
    "net/url"
)

var addr = flag.String("addr", "localhost:12345", "http service address")

// this function allows transmitting of output
// and command as chat message strings
type CommandChat struct 
    Conn *websocket.Conn


func ChatConnect() (c *CommandChat, err error) 
    u := url.URLScheme: "ws", Host: *addr, Path: "/echo"
    conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
    c = &CommandChatConn: conn
    return


func (c *CommandChat) Write(input []byte) (n int, err error) 
    err = c.Conn.WriteMessage(websocket.TextMessage, input)
    return

func (c *CommandChat) Rx(resp []byte) (err error) 
    return

func (c *CommandChat) Close() (err error)  return 

现在您可以将CommandChat 分配给os.StdOut

chat, err := ChatConnect()
cmd.Stdout = chat

reference

【讨论】:

我已经在命令完成后得到了输出,但是需要很长时间才能得到它,我需要逐行获取输出,我会显示给用户查看程序的进度 您需要在命令执行完成之前获取命令的输出吗?

以上是关于获取 os.exec 的输出以显示执行命令的进度的主要内容,如果未能解决你的问题,请参考以下文章

[go]os/exec执行shell命令

在 Go os/exec 命令中激活 Python venv

shell原地更新终端输出信息

如何从命令中获取输出以实时显示在窗体上的控件中?

Android 集成 FFmpeg 获取 FFmpeg 执行进度

ORA-03113: 在 os_command.exec 被重定向到标准输出之后,通信通道上的文件结尾