2

I have to process a long output of a script and find some data. This data most likely will be located at the almost very beginning of the output. After data found I do not need to process output anymore and can quit.

The issue that I cannot stop processing output because exec.Cmd does not have any function to close opened command.

Here are some simplified code (error handling was ommited):

func processOutput(r *bufio.Reader)(){
   for {
      line, _, err := r.ReadLine()
      if some_condition_meet {
         break
      }
      ......
   }
   return
}

func execExternalCommand(){
   cmdToExecute := "......"
   cmd := exec.Command("bash", "-c", cmdToExecute)
   output, _ := cmd.StdoutPipe()

   cmd.Start()
   r := bufio.NewReader(output)
   go processOutput(r)
   cmd.Wait()
   return
}  

What should I do at the end at processOutput function to stop cmd? Maybe there is another way how to solve it.

Thanks

ravnur
  • 2,772
  • 19
  • 28
  • What do you mean that there is no way to "close opened command"? You terminate a process by sending it a signal to exit, which you can do with the `Kill` or `Signal` methods. – JimB Aug 28 '17 at 15:33
  • If the command reads stdin and exits on EOF, then close stdin to the command. Otherwise, send a signal as mentioned in other comments. – Charlie Tumahai Aug 28 '17 at 17:15
  • Just to add a bit more detail, if you close stdin then the sub-process's stdout writes will cause it to receive a SIGPIPE signal (by default, unless this behavior is overridden). So you are sending a signal one way or another. – Zan Lynx Aug 28 '17 at 18:47

2 Answers2

3

As it stands, you can't do this from processOutput because all it receives is a bufio.Reader. You would need to pass the exec.Cmd to it for it to do anything with the forked process.

To kill the forked process, you can send it a signal, e.g.: cmd.Process.Kill() or cmd.Process.Signal(os.SIGINT). See documentation on exec.Cmd and os.Process.

Adrian
  • 42,911
  • 6
  • 107
  • 99
2

You could probably use "context" for example:

package main

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

func processOutput(r *bufio.Reader, cancel context.WithCancel) {
    for {
        line, _, err := r.ReadLine()
        if some_condition_meet {
            break
        }
    }
    cancel()
    return
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    cmdToExecute := "......"
    cmd := exec.CommandContext(ctx, "bash", "-c", cmdToExecute)
    output, _ := cmd.StdoutPipe()

    cmd.Start()
    r := bufio.NewReader(output)
    go processOutput(r, cancel)
    cmd.Wait()
    return
}

In case need to end with a timeout this could work (example is taken from here: https://golang.org/src/os/exec/example_test.go)

func ExampleCommandContext() {
    ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
    defer cancel()

    if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
    // This will fail after 100 milliseconds. The 5 second sleep
    // will be interrupted.
    }
  }

A basic example just using sleep but closing it after 1 second: https://play.golang.org/p/gIXKuf5Oga

nbari
  • 25,603
  • 10
  • 76
  • 131