Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture stdout from command exec in real time

Tags:

go

I'm currently working on a chat bot that will perform commands. One thing I want it to do is be able to run scripts and output the stdout of the script into the chat.

The issue I am having is that the function collects all of the stdout of the script and returns them all at the end, I'd like to try and modify it to write in real time and have had issues doing so.

What I think the issue might involve is that there is only one way for it to return text to the chat channel and that is via the return function of reboot. However I'd like to iterate over the exec command and output that if possible.

Here's my code:

func reboot(command *bot.Cmd) (string, error) {
    n := command.Args[0]
    // this return is what all gets sent into chat channel
    return runcommand(n), nil
  }


func runcommand(server string) string {
    cmd := exec.Command("/bin/bash", "-c", "python test.py %s", server)
    cmdOutput := &bytes.Buffer{}
    cmd.Stdout = cmdOutput
    err := cmd.Run()
    if err !=nil {
        os.Stderr.WriteString(err.Error())
    }
        return fmt.Sprintf(string(cmdOutput.Bytes()))
}
like image 867
Blooze Avatar asked Jan 20 '18 07:01

Blooze


2 Answers

A real time command pipe for redis.log. (you can call bgsave to test it.)

package main

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

func main() {
    cmd := exec.Command("tail", "-f",  "/usr/local/var/log/redis.log")

    // create a pipe for the output of the script
    cmdReader, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for Cmd", err)
        return
    }

    scanner := bufio.NewScanner(cmdReader)
    go func() {
        for scanner.Scan() {
            fmt.Printf("\t > %s\n", scanner.Text())
        }
    }()

    err = cmd.Start()
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error starting Cmd", err)
        return
    }

    err = cmd.Wait()
    if err != nil {
        fmt.Fprintln(os.Stderr, "Error waiting for Cmd", err)
        return
    }
}
like image 150
gushitong Avatar answered Sep 27 '22 17:09

gushitong


I haven't found a solution to your problem, but I have found something strange.

I wrote three versions of a program that repeatingly output a short string with one second interval, one in Python, one in C, and one in Go:

talker.py

import time

while True:
        print("Now!")
        time.sleep(1)

talker.c

#include <unistd.h>

main() {
        for(;;) {
                write(1, "Now!\n", 5);
                sleep(1);
        }
}

talker.go

package main

import (
        "fmt"
        "time"
)

func main() {
        for {
                fmt.Println("Now!")
                time.Sleep(time.Second)
        }
}

When I run them by themselves (e.g. python ./talker.py) they seem to work exactly the same. But when I pipe the output to cat I see a difference; the C and Go version gets their output through to the screen immediately, but not so the Python version. Its output gets buffered and doesn't show up on screen until enough data has been collected.

I even tried with a simple Go version of cat, and that doesn't change the behaviour:

package main

import (
        "io"
        "os"
)

func main() {
        io.Copy(os.Stdout, os.Stdin)
}
like image 37
md2perpe Avatar answered Sep 27 '22 18:09

md2perpe