You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 18:51:02