donglang6656 2016-04-01 18:25
浏览 35
已采纳

运行带有超时的命令并一次进入stdout一行

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 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 (
    "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("%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])
}

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条回答 默认 最新

  • douwen1901 2016-04-01 18:29
    关注

    I think you want to put the body of your default: case into a goroutine; I suspect it's preventing your case <-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 (
        "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("%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")
    }
    

    Which works for me

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站
  • ¥50 成都蓉城足球俱乐部小程序抢票
  • ¥15 yolov7训练自己的数据集
  • ¥15 esp8266与51单片机连接问题(标签-单片机|关键词-串口)(相关搜索:51单片机|单片机|测试代码)
  • ¥15 电力市场出清matlab yalmip kkt 双层优化问题
  • ¥30 ros小车路径规划实现不了,如何解决?(操作系统-ubuntu)