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

终端进度输出重定向至文件时多行显示的解决方法

解决进度条重定向到文件时多行输出的问题

我写了一个在终端显示实时进度条的程序,使用|& tee a.txt将输出重定向到文件时,每次进度更新都会在文件中生成一行,还混着ANSI控制字符。如何让文件内容只保留最终的进度状态,和终端最终显示的一致?

原程序代码

#include <chrono>
#include <iostream>
#include <thread>

void write_progress(const double progress, const std::chrono::seconds& elapsed_time, const std::string& display_text, const char fill = '#') {
    static constexpr size_t bar_width = 100;

    // No need to write once progress is 100%
    if (progress > 100.0) {
        return;
    }

    // Hide cursor
    std::cout << "\033[?25l";

    // Move cursor to the first position on the same line and flush
    std::cout << "\r" << std::flush;

    std::cout << display_text;

    // Write progress percentage
    std::cout << ": " << progress << "%";

    // Start of the bar
    std::cout << " [ ";

    const auto completed = static_cast<size_t>(progress * bar_width / 100.0);
    std::cout << std::string(completed, fill);
    std::cout << std::string(bar_width - completed, ' ');

    // End of the bar
    std::cout << " ] ";

    std::cout << "\tET: " << elapsed_time.count() << "s";

    // Show cursor
    std::cout << "\033[?25h";
}

void progress(const std::string& status_text) {
    std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
    for (int i = 0; i <= 100; i+= 10) {
        std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
        write_progress(static_cast<double>(i), std::chrono::duration_cast<std::chrono::seconds>(end - begin), status_text);
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
    std::cout << std::endl;
}

int main() {
    progress("Processing Status");
}

当前问题

执行命令./a.out |& tee a.txt后,a.txt内容包含所有中间进度行和ANSI控制符:

[?25l
Processing Status: 0% [                                                                                                      ]  ET: 0s[?25h[?25l
Processing Status: 10% [ ##########                                                                                           ]     ET: 1s[?25h[?25l
...
Processing Status: 100% [ #################################################################################################### ]    ET: 10s[?25h

期望效果

a.txt仅保留最终进度行:

Processing Status: 100% [ #################################################################################################### ]    ET: 10s

问题原因

  1. 终端中的\r(回车符)会让光标回到行首覆盖内容,实现单行更新,但文件不会解析该控制符,只会将其作为普通字符存储,导致每一次输出都成为新行的一部分。
  2. 程序输出的ANSI控制码(如\033[?25l隐藏光标、\033[?25h显示光标)会被写入文件,造成内容混乱。
  3. 终端是交互式设备,能解析控制符;而文件是纯文本存储,所有输出都会被原样记录。

解决方案

核心思路是区分输出目标是终端还是文件,针对不同目标输出不同内容:

  • 输出到终端时:保留实时更新和ANSI控制符,保证交互体验。
  • 输出到文件时:跳过中间进度更新,仅在进度完成时输出最终状态。

修改后的代码

#include <chrono>
#include <iostream>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

// 判断输出是否为终端
bool is_terminal() {
#ifdef _WIN32
    return GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), nullptr) != 0;
#else
    return isatty(fileno(stdout)) != 0;
#endif
}

void write_progress(const double progress, const std::chrono::seconds& elapsed_time, const std::string& display_text, const char fill = '#') {
    static constexpr size_t bar_width = 100;

    if (progress > 100.0) {
        return;
    }

    bool output_to_terminal = is_terminal();

    if (output_to_terminal) {
        // 终端模式:隐藏光标并回到行首
        std::cout << "\033[?25l\r" << std::flush;
    } else {
        // 文件模式:非100%进度不输出
        if (progress < 100.0) {
            return;
        }
    }

    // 输出进度核心内容
    std::cout << display_text;
    std::cout << ": " << progress << "%";
    std::cout << " [ ";

    const auto completed = static_cast<size_t>(progress * bar_width / 100.0);
    std::cout << std::string(completed, fill);
    std::cout << std::string(bar_width - completed, ' ');

    std::cout << " ] ";
    std::cout << "\tET: " << elapsed_time.count() << "s";

    if (output_to_terminal) {
        // 终端模式:显示光标并刷新
        std::cout << "\033[?25h" << std::flush;
    } else {
        // 文件模式:输出换行
        std::cout << std::endl;
    }
}

void progress(const std::string& status_text) {
    std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
    for (int i = 0; i <= 100; i+= 10) {
        std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
        write_progress(static_cast<double>(i), std::chrono::duration_cast<std::chrono::seconds>(end - begin), status_text);
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
    // 终端模式下最后补换行
    if (is_terminal()) {
        std::cout << std::endl;
    }
}

int main() {
    progress("Processing Status");
}

验证效果

编译运行./a.out |& tee a.txt后:

  • 终端依然实时显示进度条更新。
  • a.txt中仅保留最终进度行,与期望一致。

内容的提问来源于stack exchange,提问作者Harry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 02:44:52