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

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

报告相同问题?