Golang如何持续解析rsync命令的实时进度输出
问题原因及解决方案
你当前的代码存在几个核心问题导致无法正常读取rsync输出:
1. exec.Command 参数传递错误
exec.Command 要求命令的每个参数单独作为传入项,你将所有参数拼接为单个字符串传入,rsync无法识别该格式的参数,大概率根本没有正常启动执行,自然没有输出。
2. 行分割规则不匹配
rsync的实时进度输出使用 \r(回车符)刷新同一行内容,不会输出 \n(换行符),而 bufio.Scanner 默认按 \n 分割行,因此会一直阻塞直到命令结束才会返回最终的一行输出,无法拿到实时进度。
3. 代码存在类型错误
你声明的goroutine入参类型是 *bufio.Reader,实际传入的是 *bufio.Scanner,该代码无法正常编译运行。
可行实现代码
package main import ( "bufio" "fmt" "log" "os/exec" "regexp" ) // 自定义分割函数,同时识别\r和\n作为行结束符,适配rsync的进度刷新逻辑 func scanCRLF(data []byte, atEOF bool) (advance int, token []byte, err error) { for i := 0; i < len(data); i++ { if data[i] == '\r' || data[i] == '\n' { // 处理Windows风格的\r\n换行 if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' { return i + 2, data[:i], nil } return i + 1, data[:i], nil } } if atEOF && len(data) > 0 { return len(data), data, nil } return 0, nil, nil } func CopyWithProgress(src, dst string) { // 修正:所有参数单独传递,不要拼接为单个字符串 cmd := exec.Command("rsync", "-a", "-h", "--info=progress2", src, dst) stdout, err := cmd.StdoutPipe() if err != nil { log.Fatalf("创建stdout管道失败: %v", err) } // 可选:同时捕获stderr,防止rsync将错误或进度输出到stderr stderr, err := cmd.StderrPipe() if err != nil { log.Fatalf("创建stderr管道失败: %v", err) } if err = cmd.Start(); err != nil { log.Fatalf("启动rsync失败: %v", err) } // 同步通道,等待输出处理goroutine完成 done := make(chan struct{}, 2) // 处理stdout输出 go func() { defer close(done) scanner := bufio.NewScanner(stdout) // 设置自定义分割函数 scanner.Split(scanCRLF) // 预编译正则,不用每次循环创建 percentReg := regexp.MustCompile(`(\d+\%)`) etaReg := regexp.MustCompile(`(\d+:\d+:\d+)`) speedReg := regexp.MustCompile(`(\d+\.\d+\w{2}/s)`) for scanner.Scan() { logStr := scanner.Text() if logStr == "" { continue } // 解析进度信息 percent := percentReg.FindString(logStr) eta := etaReg.FindString(logStr) speed := speedReg.FindString(logStr) fmt.Printf("进度: %s, 速度: %s, 剩余时间: %s, 原始输出: %s\n", percent, speed, eta, logStr) } if err := scanner.Err(); err != nil { log.Printf("读取stdout失败: %v", err) } }() // 处理stderr输出 go func() { scanner := bufio.NewScanner(stderr) scanner.Split(scanCRLF) for scanner.Scan() { log.Printf("rsync 错误输出: %s", scanner.Text()) } done <- struct{}{} }() // 等待输出处理完成 <-done // 等待rsync命令执行完成 if err = cmd.Wait(); err != nil { log.Fatalf("rsync执行失败: %v", err) } }
额外优化建议
- 如果rsync检测到输出不是终端,可能会降低进度刷新频率,可以添加
--outbuf=0参数关闭rsync的输出缓冲,进一步提升实时性:cmd := exec.Command("rsync", "-a", "-h", "--info=progress2", "--outbuf=0", src, dst) - 正则匹配可以提前预编译,不要在循环内反复创建,提升性能。
内容的提问来源于stack exchange,提问作者Max Xu
相关产品推荐
相关产品推荐

