Answer the question
In order to leave comments, you need to log in
How to read stdout and stderr from a process launched via cmd.Command?
Hello, I need to read stdout and stderr from processes started by Go itself via cmd.Command. As an example, I run regular go programs that write to stdout at 2 second intervals:
The code is long, but it's simple (process.go):
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
processId := getProcessIdent()
welcomeMsg(processId)
toStdout := makeStdoutWriter(processId)
sig := makeNotifier()
loop:
for {
select {
case <-sig:
break loop
case <-time.After(time.Second * 2):
toStdout()
}
}
fmt.Println("Good Luck")
}
func welcomeMsg(processId int) {
fmt.Println(fmt.Sprintf("Welcome to Process %d", processId))
}
func getProcessIdent() int {
var processId int
flag.IntVar(&processId, "id", 999, "number of lines to read from the file")
flag.Parse()
return processId
}
func makeNotifier() <-chan os.Signal {
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
return sig
}
func makeStdoutWriter(processId int) func() {
write := func(processId int) {
fmt.Fprintln(os.Stdout, fmt.Sprintf("Process work - %d", processId))
}
return func() {
write(processId)
write(processId)
write(processId)
}
}
type Executor struct {
cmd *exec.Cmd
r *io.PipeReader
w *io.PipeWriter
}
func executeAnotherGo(id int) (*Executor, error) {
args := []string{
"run", "./process.go", "-id", strconv.Itoa(id),
}
r, w := io.Pipe()
cmd := exec.Command("go", args...)
cmd.Stdout = w
cmd.Stderr = w
if err := cmd.Start(); err != nil {
return nil, err
}
return &Executor{cmd: cmd, r: r, w: w}, nil
}
func perform(ctx context.Context, id int) {
executor, err := executeAnotherGo(id)
if err != nil {
log.Fatal("Error execute process ", id, "cause: ", err)
}
writer, err := os.OpenFile(fmt.Sprintf("./process_%d.log", id), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println(err)
return
}
localContext, cancel := context.WithCancel(ctx)
defer func() {
// close pipe writer
if err := executor.w.Close(); err != nil {
fmt.Println(err)
}
// try kill process
if err := executor.cmd.Process.Kill(); err != nil {
fmt.Println(id, executor.cmd.Process.Pid, err)
}
cancel()
}()
buf := make([]byte, 1024)
ffmpegOutput := make(chan []string)
processKilled := make(chan error, 1)
// Runs a separate sub-thread, because when running in a single thread,
// there is a lock while waiting for the buffer to be read.
// In turn blocking by the reader will not allow the background task to finish gracefully
go func() {
for {
count, err := executor.r.Read(buf)
if err != nil {
fmt.Println("Close reader, cause: ", err)
return
}
buf = buf[:count]
str := string(buf)
parts := strings.Split(strings.TrimSpace(str), "\n")
if len(parts) <= 1 {
continue
}
ffmpegOutput <- parts
fmt.Println(str)
}
}()
// We listen to the process termination signal,
// this will provide an opportunity to remove the task from the pool and restart it if necessary
//
// Note: We listen to the context so as not to leave active goroutines when the task is completed
go func() {
select {
case processKilled <- executor.cmd.Wait():
return
case <-localContext.Done():
return
}
}()
loop:
for {
select {
case <-localContext.Done():
fmt.Println("Cancel process: ", id)
break loop
case err := <-processKilled:
fmt.Println("Killed", id, executor.cmd.Process.Pid, err)
break loop
case outPartials := <-ffmpegOutput:
if _, err := writer.WriteString(strings.Join(outPartials, "\n")); err != nil {
fmt.Println(err)
}
}
}
}
func main() {
fmt.Println("Run program, wait processes")
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
ctx, cancel := context.WithCancel(context.Background())
go perform(ctx, 1)
go perform(ctx, 2)
go perform(ctx, 3)
<-sig
// cancel all sub process
cancel()
// wait all canceled
<-time.After(time.Second * 2)
fmt.Println("graceful exit")
}
r, w := io.Pipe()
cmd := exec.Command("go", args...)
// заменяем
// cmd.Stdout = w
// cmd.Stderr = w
// на
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
bufioReader := bufio.NewReader(executor.r)
for {
line, _, err := bufioReader.ReadLine()
strings.Join(outPartials, "\n") - убрать новую строку
Answer the question
In order to leave comments, you need to log in
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question