用\r回车覆盖实现进度条:始终用fmt.Print/Printf,避免Println换行;刷新前先输出\r,再输出带空格填充的进度字符串;任务结束补\n。

用 fmt.Print 覆盖上一行实现简单进度条
终端里没法“更新”已输出的文字,但可以靠回车符 \r 把光标拉回行首,再用新内容覆盖旧内容——这是最轻量、兼容性最好的做法,不需要第三方库,也不依赖 ANSI 转义序列是否被支持。
常见错误是混用 fmt.Println(自动换行)或忘记清空残留字符,导致进度条错位、重叠、显示乱码。
- 始终用
fmt.Print或fmt.Printf,避免fmt.Println - 每次刷新前先输出
\r,再输出带空格填充的进度字符串(防止上一次内容更长时残留) - 任务结束时补一个换行
\n,否则光标会卡在行中影响后续输出
for i := 0; i <= 100; i++ {
time.Sleep(50 * time.Millisecond)
bar := strings.Repeat("█", i/2) + strings.Repeat("░", 50-i/2)
fmt.Printf("\r[%s] %d%%", bar, i)
}
fmt.Print("\n")
github.com/mitchellh/go-wordwrap 不适合进度条,别踩这个坑
搜 “Go 进度条” 常看到有人推荐引入 go-wordwrap 或类似文本处理库——它完全无关。进度条核心是控制光标位置和覆盖输出,不是文本折行。引入这类库只会增加编译体积、启动延迟,还可能因依赖冲突破坏构建。
真正该关注的是终端能力检测:比如 CI 环境(GitHub Actions、GitLab CI)通常不支持 \r 覆盖,输出会堆成多行;Windows 的旧版 cmd 也对某些转义序列支持不全。
立即学习“go语言免费学习笔记(深入)”;
- 用
os.Getenv("CI")或os.Getenv("TERM") == ""判断是否在非交互终端 - 非交互环境下直接关闭进度条,只输出关键日志,不要强行模拟
- Windows 上优先测试 PowerShell 和 Windows Terminal,传统 cmd 可降级为纯百分比数字输出
用 golang.org/x/term 检测终端宽度做自适应进度条
固定宽度的进度条在小窗口里会换行,在大屏上又太窄。用 term.GetSize 获取当前终端列数,就能动态缩放进度条长度,体验更稳。
注意:该函数在非 TTY 环境(如管道重定向、Docker exec -it 未分配伪终端)会返回错误,不能 panic,必须兜底。
- 调用前检查
os.Stdin.Stat().Mode() & os.ModeCharDevice != 0确认是真实终端 - 获取失败时设默认宽度(比如 60),而非硬写死 80
- 进度条总长建议留 20 字符余量给百分比、括号、空格等固定部分
if width, _, err := term.GetSize(int(os.Stdin.Fd())); err == nil {
barWidth = max(20, width-25)
}并发任务里多个进度条互相干扰怎么办
多个 goroutine 同时往 os.Stdout 写 \r 行,极易出现文字撕裂、覆盖错位——这不是 Go 并发模型的问题,而是 stdout 是共享文件描述符,没有内置行级锁。
根本解法不是加 mutex(会串行化输出、拖慢响应),而是让每个任务走独立输出通道,由单个 goroutine 统一调度渲染。
- 每个任务把进度封装成结构体(
struct{ ID string; Pct int; Done bool }),发到全局 channel - 主渲染 goroutine 每 50ms 拉取一次全量状态,按 ID 排序后逐行打印(用
\033[A上移光标控制位置) - 避免在 goroutine 里直接调用
fmt.Print,哪怕加了锁,也会因调度延迟造成视觉抖动
终端宽度变化、后台作业抢占、SSH 连接中断……这些边界情况不会报错,但会让进度条突然失序。真要健壮,就得在每次渲染前做最小可行性校验:是不是还在 TTY?上次输出距今是否超时?有没有新行被意外写入?这些细节比选哪个库重要得多。


















