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 执行 virtuoso 命令后,界面没有,cadence 启动不起来
  • ¥50 comfyui下连接animatediff节点生成视频质量非常差的原因
  • ¥20 有关区间dp的问题求解
  • ¥15 多电路系统共用电源的串扰问题
  • ¥15 slam rangenet++配置
  • ¥15 有没有研究水声通信方面的帮我改俩matlab代码
  • ¥15 ubuntu子系统密码忘记
  • ¥15 信号傅里叶变换在matlab上遇到的小问题请求帮助
  • ¥15 保护模式-系统加载-段寄存器
  • ¥15 电脑桌面设定一个区域禁止鼠标操作