如何在golang中获取shell命令的实时输出?

我正在尝试在golang中使用os / exec调用shell命令,该命令将花费一些时间,所以我会 想要检索reatime输出并打印处理后的输出(进度比率编号)。</ p>

 软件包main 

import(
“ bufio”
“ fmt” \ n“ io”
“ os”
“ os / exec”
“ strings”

func main(){
cmdName:=“ ffmpeg -i t.webm -acodec aac -vcodec libx264 cmd1.mp4“
cmdArgs:= strings.Fields(cmdName)

cmd:= exec.Command(cmdArgs [0],cmdArgs [1:len(cmdArgs)] ...)
标准输出,_: = cmd.StdoutPipe()
cmd.Start()
go print(stdout)
cmd.Wait()
}

//在stdout换行时打印处理过的信息
func打印 (stdout io.ReadCloser){
r:= bufio.NewReader(stdout)
line,_,err:= r.ReadLine()
fmt.Println(“ line:%s err%s”,line,err )
}
</ code> </ pre>

我想拥有一个功能,当命令打印某些内容时,可以更新屏幕</ p>
\ n

ffmpeg命令输出如下:</ p>

  frame = 101 fps = 0.0 q = 28.0 size = 91kB time = 00:00:04.13 bitrate = 181.2kbits /

frame = 169 fps = 168 q = 28.0大小= 227kB时间= 00:00:06.82比特率= 272.6kbits /
frame = 231 fps = 153 q = 28.0大小= 348kB时间= 00:00:09.31比特率= 306.3kbits /

frame = 282 fps = 140 q = 28.0大小= 499kB时间= 00:00:11.33比特率= 360.8kbits /
</ code> </ pre>

实际上,以上4行是 ffmpeg命令输出的最后一行不断更改,我想将更改打印出来,例如</ p>

  18%
44%
69%
100%
</ code > </ pre>

我怎么能做到这一点?</ p>
</ div>

展开原文

原文

I am trying to call shell command with os/exec in golang, that command will take some time, so I would like to retrieve the reatime output and print the processed output (a progressing ratio number).

package main

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

func main() {
    cmdName := "ffmpeg -i t.webm  -acodec aac -vcodec libx264  cmd1.mp4"
    cmdArgs := strings.Fields(cmdName)

    cmd := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...)
    stdout, _ := cmd.StdoutPipe()
    cmd.Start()
    go print(stdout)
    cmd.Wait()
}

// to print the processed information when stdout gets a new line
func print(stdout io.ReadCloser) {
    r := bufio.NewReader(stdout)
    line, _, err := r.ReadLine()
    fmt.Println("line: %s err %s", line, err)
}

I want to have a function where can update the screen when the command print something,

The ffmpeg command output is as follows:

frame=  101 fps=0.0 q=28.0 size=      91kB time=00:00:04.13 bitrate= 181.2kbits/
frame=  169 fps=168 q=28.0 size=     227kB time=00:00:06.82 bitrate= 272.6kbits/
frame=  231 fps=153 q=28.0 size=     348kB time=00:00:09.31 bitrate= 306.3kbits/
frame=  282 fps=140 q=28.0 size=     499kB time=00:00:11.33 bitrate= 360.8kbits/

in fact, the above 4 line is the last line of ffmpeg command output which keeps changing, I want to print that change out, like

18%
44%
69%
100%

how could I achieve this?

dongqishou7471
dongqishou7471 我也在这里添加了答案。
4 年多之前 回复
dongyuan2388
dongyuan2388 您能否看一下我发布的答案,为什么我的屏幕上什么都没打印出来?
4 年多之前 回复
dounan9070
dounan9070 流命令输出进度可能重复
4 年多之前 回复
dtch60248
dtch60248 fmt库具有一组扫描功能,可以将格式化的字符串解析为值。您可以在标准输出管道上使用扫描仪来扫描行并扫描每条格式化的行。您需要弄清楚如何从ffmpeg中获得完整与总计的比率。
4 年多之前 回复

4个回答



我确实发现他在那篇文章中提到的icza解决方案非常有用,但是并不能解决我的问题。</ p>

我做了如下测试:</ p>

1,我编写了一个脚本,该脚本每秒打印一些信息十次,这是script.sh </ p>

 #!/ bin / bash 

for {1..10}
do
中的我echo“ step” $ i
sleep 1s
done
</ code> </ pre>

2,读取标准输出,并从标准输出中提取所需的信息,并执行一些过程以获取所需的格式,这是代码:
package main </ p> \ n

  import(
“ fmt”
“ os / exec”
“ regexp”
“ strconv”
“ strings”

func getRatio(文本字符串) float32 {
re1,_:= regexp.Compile(step [\ s] +(\ d +)
结果:= re1.FindStringSubmatch(text)
val,_:= strconv.Atoi(result [ 1])
返回float32(val)/ 10
}

func main(){
cmdName:=“ ffmpeg -i t.webm -acodec aac -vcodec libx264 cmd1.mp4”
// cmdName :=“ bash ./script.sh"
cmdArgs:= strings.Fields(cmdName)

cmd:= exec.Command(cmdArgs [0],cmdArgs [1:len(cmdArgs)] ...)
stdout,_:= cmd.StdoutPipe()

cmd.Start()

oneByte:= make([] byte,10)
for {
_,err:= stdout.Read(oneByte)
if err!= nil {
break

}
progressingRatio:= getRatio(string(oneByte))
fmt.Printf(“ progressing ratio%v
”,progressingRatio)
}
}
</ code> </ pre>

这确实适用于我的script.sh测试,但对于ffmpeg命令却不起作用,在ffmpeg的情况下,没有任何内容被打印并且进程完成(未卡住),我想是写数据的方式 ffmpeg的stdout有点特殊(也许根本没有换行符,我尝试了icza的解决方案,但仍然不起作用)。</ p>
</ div>

展开原文

原文

I do find icza's solution that he mentioned in that post is quite useful, however it didn't't solve my problem.

I did a little test as following:

1, I write a script which print some info every second for ten times, here is the script.sh

#!/bin/bash

for i in {1..10}
do
    echo "step " $i
    sleep 1s
done

2, read the stdout and extract the needed information from stdout and do some process to get the expected format, here is the code: package main

import (
    "fmt"
    "os/exec"
    "regexp"
    "strconv"
    "strings"
)

func getRatio(text string) float32 {
    re1, _ := regexp.Compile(`step[\s]+(\d+)`)
    result := re1.FindStringSubmatch(text)
    val, _ := strconv.Atoi(result[1])
    return float32(val) / 10
}

func main() {
    cmdName := "ffmpeg -i t.webm  -acodec aac -vcodec libx264  cmd1.mp4"
    //cmdName := "bash ./script.sh"
    cmdArgs := strings.Fields(cmdName)

    cmd := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...)
    stdout, _ := cmd.StdoutPipe()
    cmd.Start()

    oneByte := make([]byte, 10)
    for {
        _, err := stdout.Read(oneByte)
        if err != nil {
            break
        }
        progressingRatio := getRatio(string(oneByte))
        fmt.Printf("progressing  ratio %v 
", progressingRatio)
    }
}

This does work for my script.sh test, but for the ffmpeg command it doesn't work, in ffmpeg's case, nothing get printed and the process get finished (not stuck), I guess the way of writing data to stdout for ffmpeg is a little special (maybe no newline character at all, and I tried icza's solution, but it still doesn't work).

dongmei9203
dongmei9203 您可以在伪终端中运行ffmpeg,这可能会起作用(?)
大约 3 年之前 回复



当您拥有 exec.Cmd </ code> 值,您可以使用其 Cmd.Stdin </ code>, Cmd.Stdout </ code>和 Cmd.Stderr </ code>字段以 some </ em>方式与进程通信。</ p>

有些</ em >方法意味着您可以将数据发送到其 standard </ em>输入,并可以读取其 standard </ em>输出和错误流。</ p>

压力 符合标准</ em>。 如果外部进程正在通过网络连接发送数据,或者正在将数据写入文件,您将无法通过上述3个流截取这些数据。</ p>

现在 <代码> ffmpeg的</代码>。 ffmpeg </ code>和许多其他控制台应用程序不将数据写入标准输出/错误,但是它们使用系统调用或其他库(使用系统调用)来操纵终端窗口。 当然,应用程序可以向标准输出/错误发送 some </ em>数据,并可以通过操作终端窗口来显示 other </ em>数据。</ p>

因此您看不到 ffmpeg </ code>的输出,因为您尝试读取其标准输出/错误,但是 ffmpeg </ code>不会通过写入这些流来显示其输出。 / p>

在一般情况下,如果要捕获此类应用程序的输出,则需要一个能够捕获终端窗口(文本)内容的库。 在较容易的情况下,应用程序支持将这些输出转储到通常由额外的命令行参数控制的文件中,然后您可以从Go中读取/监视。</ p>
</ div>

展开原文

原文

When you have an exec.Cmd value of an external command you started from Go, you may use its Cmd.Stdin, Cmd.Stdout and Cmd.Stderr fields to communicate with the process in some way.

Some way means you can send data to its standard input, and you can read its standard output and error streams.

The stress is on standard. If the external process is sending data on a network connection, or is writing data to a file, you will not be able to intercept those data via the above mentioned 3 streams.

Now on to ffmpeg. ffmpeg and many other console applications do not write data to standard output/error, but they use system calls or other libraries (that use system calls) to manipulate the terminal window. Of course an application may send some data to the standard output/error, and may display other data by manipulating the terminal window.

So you don't see the output of ffmpeg because you try to read its standard output/error, but ffmpeg does not display its output by writing to those streams.

In the general case if you want to capture the output of such applications, you need a library that is capable of capturing the (textual) content of the terminal window. In an easier situation the application supports dumping those output to files usually controlled by extra command line parameters, which then you can read/monitor from Go.

doucong3048
doucong3048 谢谢。 在此期间,我找到了github.com/creack/pty,它做得很好。
12 个月之前 回复
dqitk20644
dqitk20644 不,我什么都没有。 我以为像termbox-go这样的库可以做到这一点,但似乎它只能从其缓冲区返回字符。
12 个月之前 回复
duanli0453
duanli0453 “您需要一个能够捕获终端窗口(文本)内容的库”>您对此有何建议? 这是朝着需要PTY的方向前进吗?
12 个月之前 回复



类似于ffmpeg,它会将所有诊断消息(“控制台输出”)
发送到 stderr </ strong>而不是stdout。 下面的代码对我有用。 </ p>

 包main 

import(
“ bufio”
“ fmt”
“ os / exec”
“ strings”

func main(){
args:=“ -i test.mp4 -acodec copy -vcodec copy -f flv rtmp:// aaa / bbb”
cmd:= exec.Command(“ ffmpeg”,strings.Split(args ,“”)...)

stderr,_:= cmd.StderrPipe()
cmd.Start()

扫描仪:= bufio.NewScanner(stderr)
扫描仪.Split(bufio。 ScanWords)
for scan.Scan(){
m:= scaner.Text()
fmt.Println(m)
}
cmd.Wait()
}
</ code> </ pre >

ffmpeg的版本详细说明如下。 </ p>

  ffmpeg版本3.0.2版权所有(c)2000-2016使用Apple LLVM版本7.3.0(clang-703.0.29)
构建的FFmpeg开发人员
配置:- 前缀= / usr / local / Cellar / ffmpeg / 3.0.2 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc = clang --host-cflags = --host-ldflags = --enable-opencl --enable-libx264 --enable-libmp3lame --enable-libxvid --enable-vda
libavutil 55. 17.103 / 55. 17.103
libavcodec 57 。24.102 / 57. 24.102
libavformat 57. 25.100 / 57. 25.100
libavdevice 57. 0.101 / 57. 0.101
libavfilter 6. 31.100 / 6. 31.100
libavresample 3. 0. 0/3。0. 0
libswscale 4。 0.100 / 4. 0.100
libswresample 2. 0.101 / 2. 0.101
libpostproc 54. 0.100 / 54. 0.100
</ code> </ pre>
</ div>

展开原文

原文

Looks like ffmpeg sends all diagnostic messages (the "console output") to stderr instead of stdout. Below code works for me.

package main

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

func main() {
    args := "-i test.mp4 -acodec copy -vcodec copy -f flv rtmp://aaa/bbb"
    cmd := exec.Command("ffmpeg", strings.Split(args, " ")...)

    stderr, _ := cmd.StderrPipe()
    cmd.Start()

    scanner := bufio.NewScanner(stderr)
    scanner.Split(bufio.ScanWords)
    for scanner.Scan() {
        m := scanner.Text()
        fmt.Println(m)
    }
    cmd.Wait()
}

The version of ffmpeg is detailed as below.

ffmpeg version 3.0.2 Copyright (c) 2000-2016 the FFmpeg developers
built with Apple LLVM version 7.3.0 (clang-703.0.29)
configuration: --prefix=/usr/local/Cellar/ffmpeg/3.0.2 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-opencl --enable-libx264 --enable-libmp3lame --enable-libxvid --enable-vda
libavutil      55. 17.103 / 55. 17.103
libavcodec     57. 24.102 / 57. 24.102
libavformat    57. 25.100 / 57. 25.100
libavdevice    57.  0.101 / 57.  0.101
libavfilter     6. 31.100 /  6. 31.100
libavresample   3.  0.  0 /  3.  0.  0
libswscale      4.  0.100 /  4.  0.100
libswresample   2.  0.101 /  2.  0.101
libpostproc    54.  0.100 / 54.  0.100

check the below, needs enhancements (not recommended to be used as it is) but working :)

package main

import (
    "fmt"
    "os"
    "os/exec"
    "strconv"
    "strings"
)

var duration = 0
var allRes = ""
var lastPer = -1

func durToSec(dur string) (sec int) {
    durAry := strings.Split(dur, ":")
    if len(durAry) != 3 {
        return
    }
    hr, _ := strconv.Atoi(durAry[0])
    sec = hr * (60 * 60)
    min, _ := strconv.Atoi(durAry[1])
    sec += min * (60)
    second, _ := strconv.Atoi(durAry[2])
    sec += second
    return
}
func getRatio(res string) {
    i := strings.Index(res, "Duration")
    if i >= 0 {

        dur := res[i+10:]
        if len(dur) > 8 {
            dur = dur[0:8]

            duration = durToSec(dur)
            fmt.Println("duration:", duration)
            allRes = ""
        }
    }
    if duration == 0 {
        return
    }
    i = strings.Index(res, "time=")
    if i >= 0 {

        time := res[i+5:]
        if len(time) > 8 {
            time = time[0:8]
            sec := durToSec(time)
            per := (sec * 100) / duration
            if lastPer != per {
                lastPer = per
                fmt.Println("Percentage:", per)
            }

            allRes = ""
        }
    }
}

func main() {
    os.Remove("cmd1.mp4")
    cmdName := "ffmpeg -i 1.mp4  -acodec aac -vcodec libx264  cmd1.mp4 2>&1"
    cmd := exec.Command("sh", "-c", cmdName)
    stdout, _ := cmd.StdoutPipe()
    cmd.Start()
    oneByte := make([]byte, 8)
    for {
        _, err := stdout.Read(oneByte)
        if err != nil {
            fmt.Printf(err.Error())
            break
        }
        allRes += string(oneByte)
        getRatio(allRes)
    }
}
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问