英文:
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 (
"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])
}
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 <-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("\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")
}
Which works for me
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论