Run command with timeout and read stdout one line at a time in go

huangapple go评论85阅读模式
英文:

Run command with timeout and read stdout one line at a time in go

问题

我想运行一个命令,并打印其stdout中的每一行(一旦可用)。此外,如果命令在N秒内没有完成,我也想终止它。

在golang中有一些实现超时的示例(特别是https://stackoverflow.com/questions/11886531/terminating-a-process-started-with-os-exec-in-golang)。我在select中有一个time.After()子句,我希望在2秒后触发,此时RunTraceroute应该返回,但实际上并没有发生。

我的代码如下(也可以在go playground上查看:http://play.golang.org/p/D4AcoaweMt)

package main

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

func RunTraceroute(host string) {
	errch := make(chan error, 1)
	cmd := exec.Command("/usr/bin/traceroute", host)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	go func() {
		errch <- cmd.Wait()
	}()
	select {
	case <-time.After(time.Second * 2):
		log.Println("Timeout hit..")
		return
	case err := <-errch:
		if err != nil {
			log.Println("traceroute failed:", err)
		}
	default:
		for _, char := range "|/-\\" {
			fmt.Printf("\r%s...%c", "Running traceroute", char)
			time.Sleep(100 * time.Millisecond)
		}
		scanner := bufio.NewScanner(stdout)
		fmt.Println("")
		for scanner.Scan() {
			line := scanner.Text()
			log.Println(line)
		}
	}
}

func main() {
	RunTraceroute(os.Args[1])
}

输出(省略了前几行本地IP地址和网关):

$ go run pipe.go xinhua.com
Running traceroute...\
2016/04/01 11:16:43 traceroute to xinhua.com (58.64.200.76), 30 hops max, 60 byte packets
.......
.......
.....More deleted lines.....

2016/04/01 11:16:49 12  * * *
2016/04/01 11:16:49 13  4.68.63.214 (4.68.63.214)  3.208 ms  3.176 ms  3.241 ms
2016/04/01 11:16:49 14  * * *
2016/04/01 11:16:49 15  if-ae-9-2.tcore1.TV2-Tokyo.as6453.net (180.87.180.18)  160.314 ms  158.837 ms  161.438 ms
2016/04/01 11:16:49 16  if-ae-5-7.tcore1.HK2-Hong-Kong.as6453.net (180.87.112.189)  157.497 ms if-ae-3-2.tcore1.HK2-Hong-Kong.as6453.net (180.87.112.5)  161.397 ms if-ae-5-7.tcore1.HK2-Hong-Kong.as6453.net (180.87.112.189)  159.351 ms
2016/04/01 11:16:49 17  if-ge-10-0-0-1128.core1.undefined.as6453.net (180.87.160.73)  156.412 ms  156.522 ms if-ge-14-0-0-1126.core1.undefined.as6453.net (180.87.112.30)  156.605 ms
2016/04/01 11:16:49 18  * * *
2016/04/01 11:16:49 19  * * *
2016/04/01 11:16:49 20  * * *
2016/04/01 11:16:49 21  113.10.229.113 (113.10.229.113)  165.578 ms  165.818 ms  163.451 ms
2016/04/01 11:16:49 22  113.10.229.74 (113.10.229.74)  163.564 ms ae5.10g-idc.wpc.nwtgigalink.com (113.10.229.66)  162.384 ms 113.10.229.74 (113.10.229.74)  167.026 ms
2016/04/01 11:16:49 23  113.10.230.162 (113.10.230.162)  162.988 ms  162.777 ms  163.807 ms
2016/04/01 11:16:49 24  58.64.160.164 (58.64.160.164)  161.902 ms  162.396 ms  164.986 ms
2016/04/01 11:16:54 25  * * *
2016/04/01 11:16:54 26  58.64.200.76 (58.64.200.76)  162.178 ms !X  162.509 ms !X  162.356 ms !X
英文:

I'd like to run a command and print each line from its stdout (as it becomes available). Further, if the command doesn't complete within N seconds, I would like to terminate it as well.

There are some examples of implementing timeouts in golang (notably https://stackoverflow.com/questions/11886531/terminating-a-process-started-with-os-exec-in-golang). I have a time.After() clause in select that I expect to hit after 2 seconds, at which point RunTraceroute should return - but this doesn't happen.

My code is below (and on go playground: http://play.golang.org/p/D4AcoaweMt)

package main

import (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;os&quot;
	&quot;os/exec&quot;
	&quot;time&quot;
)

func RunTraceroute(host string) {
	errch := make(chan error, 1)
	cmd := exec.Command(&quot;/usr/bin/traceroute&quot;, host)
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	go func() {
		errch &lt;- cmd.Wait()
	}()
	select {
	case &lt;-time.After(time.Second * 2):
		log.Println(&quot;Timeout hit..&quot;)
		return
	case err := &lt;-errch:
		if err != nil {
			log.Println(&quot;traceroute failed:&quot;, err)
		}
	default:
		for _, char := range &quot;|/-\\&quot; {
			fmt.Printf(&quot;\r%s...%c&quot;, &quot;Running traceroute&quot;, char)
			time.Sleep(100 * time.Millisecond)
		}
		scanner := bufio.NewScanner(stdout)
		fmt.Println(&quot;&quot;)
		for scanner.Scan() {
			line := scanner.Text()
			log.Println(line)
		}
	}
}

func main() {
	RunTraceroute(os.Args[1])
}

Output (snipped out first few lines of local ip addresses and gateway):

$ go run pipe.go xinhua.com
Running traceroute...\
2016/04/01 11:16:43 traceroute to xinhua.com (58.64.200.76), 30 hops max, 60 byte packets
.......
.......
.....More deleted lines.....

2016/04/01 11:16:49 12  * * *
2016/04/01 11:16:49 13  4.68.63.214 (4.68.63.214)  3.208 ms  3.176 ms  3.241 ms
2016/04/01 11:16:49 14  * * *
2016/04/01 11:16:49 15  if-ae-9-2.tcore1.TV2-Tokyo.as6453.net (180.87.180.18)  160.314 ms  158.837 ms  161.438 ms
2016/04/01 11:16:49 16  if-ae-5-7.tcore1.HK2-Hong-Kong.as6453.net (180.87.112.189)  157.497 ms if-ae-3-2.tcore1.HK2-Hong-Kong.as6453.net (180.87.112.5)  161.397 ms if-ae-5-7.tcore1.HK2-Hong-Kong.as6453.net (180.87.112.189)  159.351 ms
2016/04/01 11:16:49 17  if-ge-10-0-0-1128.core1.undefined.as6453.net (180.87.160.73)  156.412 ms  156.522 ms if-ge-14-0-0-1126.core1.undefined.as6453.net (180.87.112.30)  156.605 ms
2016/04/01 11:16:49 18  * * *
2016/04/01 11:16:49 19  * * *
2016/04/01 11:16:49 20  * * *
2016/04/01 11:16:49 21  113.10.229.113 (113.10.229.113)  165.578 ms  165.818 ms  163.451 ms
2016/04/01 11:16:49 22  113.10.229.74 (113.10.229.74)  163.564 ms ae5.10g-idc.wpc.nwtgigalink.com (113.10.229.66)  162.384 ms 113.10.229.74 (113.10.229.74)  167.026 ms
2016/04/01 11:16:49 23  113.10.230.162 (113.10.230.162)  162.988 ms  162.777 ms  163.807 ms
2016/04/01 11:16:49 24  58.64.160.164 (58.64.160.164)  161.902 ms  162.396 ms  164.986 ms
2016/04/01 11:16:54 25  * * *
2016/04/01 11:16:54 26  58.64.200.76 (58.64.200.76)  162.178 ms !X  162.509 ms !X  162.356 ms !X

答案1

得分: 3

我认为你想将default:情况的代码放入一个goroutine中;我怀疑这会阻止case <-time.After(time.Second * 2):标签被执行。

此外,请记住,time.After不能保证在指定的时间后准确触发,它只表示在该时间之后的任何时间都会向通道发送信号,这可能会在指定的时间之后的一段时间内发生。请参阅底层的time.NewTimer文档。

我修改了你的示例:http://play.golang.org/p/TggNQ1d57Y

package main

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

func RunTraceroute(host string) {
	errch := make(chan error, 1)
	cmd := exec.Command("/usr/bin/traceroute", host)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}

	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	go func() {
		errch <- cmd.Wait()
	}()

	go func() {
		for _, char := range "|/-\\" {
			fmt.Printf("\r%s...%c", "Running traceroute", char)
			time.Sleep(100 * time.Millisecond)
		}
		scanner := bufio.NewScanner(stdout)
		fmt.Println("")
		for scanner.Scan() {
			line := scanner.Text()
			log.Println(line)
		}
	}()

	select {
	case <-time.After(time.Second * 1):
		log.Println("Timeout hit..")
		return
	case err := <-errch:
		if err != nil {
			log.Println("traceroute failed:", err)
		}
	}
}

func main() {
	RunTraceroute("8.8.8.8")
}

这对我有效。

英文:

I think you want to put the body of your default: case into a goroutine; I suspect it's preventing your case &lt;-time.After(time.Second * 2): label from being hit.

Also, keep in mind that time.After does not guarantee that it will be hit exactly after that duration, it only says that any time after that duration it will send the signal on the channel, which could be a while after the designated duration. See the docs on the underlying time.NewTimer

I modified your example: http://play.golang.org/p/TggNQ1d57Y

package main

import (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;os/exec&quot;
	&quot;time&quot;
)

func RunTraceroute(host string) {
	errch := make(chan error, 1)
	cmd := exec.Command(&quot;/usr/bin/traceroute&quot;, host)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}

	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	go func() {
		errch &lt;- cmd.Wait()
	}()

	go func() {
		for _, char := range &quot;|/-\\&quot; {
			fmt.Printf(&quot;\r%s...%c&quot;, &quot;Running traceroute&quot;, char)
			time.Sleep(100 * time.Millisecond)
		}
		scanner := bufio.NewScanner(stdout)
		fmt.Println(&quot;&quot;)
		for scanner.Scan() {
			line := scanner.Text()
			log.Println(line)
		}
	}()

	select {
	case &lt;-time.After(time.Second * 1):
		log.Println(&quot;Timeout hit..&quot;)
		return
	case err := &lt;-errch:
		if err != nil {
			log.Println(&quot;traceroute failed:&quot;, err)
		}
	}
}

func main() {
	RunTraceroute(&quot;8.8.8.8&quot;)
}

Which works for me

huangapple
  • 本文由 发表于 2016年4月2日 02:25:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/36363355.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定