C++ 倒带 std::cout 回到行首

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3057977/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 11:55:42  来源:igfitidea点击:

Rewinding std::cout to go back to the beginning of a line

c++macosterminalstdoutoutput-formatting

提问by fbrereto

I'm writing a command-line tool for Mac OS X that processes a bunch of files. I would like to show the user the current file being processed, but do not want a bazillion files polluting the terminal window.

我正在为 Mac OS X 编写一个命令行工具来处理一堆文件。我想向用户显示正在处理的当前文件,但不希望大量文件污染终端窗口。

Instead I would like to use a single line to output the file path, then reuse that line for the next file. Is there a character (or some other code) to output to std::coutto accomplish this?

相反,我想使用一行来输出文件路径,然后将该行用于下一个文件。是否有一个字符(或其他一些代码)要输出std::cout来完成这个任务?

Also, if I wanted to re-target this tool for Windows, would the solution be the same for both platforms?

另外,如果我想为 Windows 重新定位此工具,两个平台的解决方案是否相同?

回答by Logan Capaldo

"\r" should work for both windows and Mac OS X.

"\r" 应该适用于 Windows 和 Mac OS X。

Something like:

就像是:

std::cout << "will not see this\rwill see this" << std::flush;
std::cout << std::endl; // all done

回答by Nathan Ernst

I don't have access to a mac, but from a pure console standpoint, this is going to be largely dependent on how it treats the carriage return and line-feed characters. If you can literally send one or the other to the console, you want to send justa carriage return.

我无法访问 mac,但从纯控制台的角度来看,这在很大程度上取决于它如何处理回车和换行符。如果你可以从字面上发送一个或另一个控制台,您要发送只是一个回车。

I'm pretty sure Mac treats both carriage returns and line-feeds differently than *nix & windows.

我很确定 Mac 对回车和换行的处理方式与 *nix 和 windows 不同。

If you're looking for in-place updates (e.g. overwrite the current line), I'd recommend looking at the curseslib. This should provide a platform independent means of doing what you're looking for. (because, even using standard C++, there is no platform independent means of what you're asking for).

如果您正在寻找就地更新(例如覆盖当前行),我建议您查看curseslib。这应该提供一个独立于平台的方法来做你正在寻找的东西。(因为,即使使用标准 C++,也没有平台独立的方式来满足您的要求)。

回答by Tony Delroy

As Nathan Ernst's answer says, if you want a robust, proper way to do this, use curses - specifically ncurses.

正如内森·恩斯特 (Nathan Ernst) 的回答所说,如果您想要一种稳健、正确的方法来做到这一点,请使用 Curses - 特别是ncurses

If you want a low-effort hackish way that tends to work, carry on...

如果你想要一种容易奏效的、省力的hackish方式,请继续......

Command-line terminals for Linux, UNIX, MacOS, Windows etc. tend to support a small set of basic ASCII control characters, including character 13 decimal - known as a Carriage Return and encoded in C++as '\r' or equivalently in octal '\015' or hex '\x0D' - instructing the terminal to return to the start of the line.

Linux、UNIX、MacOS、Windows 等的命令行终端倾向于支持一小组基本的 ASCII 控制字符,包括字符 13 十进制 - 称为回车并在 C++ 中编码为 '\r' 或等效的八进制 ' \015' 或十六进制 '\x0D' - 指示终端返回到行首。

What you generally want to do is...

你通常想做的是...

int line_width = getenv("COLUMNS") ? atoi(getenv("COLUMNS")) : 80;
std::string spaces{line_width - 1, ' '};
for (const auto& filename : filenames) {
    std::cout << '\r' << spaces << '\r' << filename << std::flush;
    process_file(filename);
}
std::cout << std::endl; // move past last filename...

This uses a string of spaces to overwrite the old filename before writing the next one, so if you have a shorter filename you don't see trailing characters from the earlier longer filename(s).

这使用一串空格在写入下一个之前覆盖旧文件名,因此如果您的文件名较短,则不会看到较早的较长文件名的尾随字符。

The std::flushensures the C++ program calls the OS write()function to send the text to the terminal before starting to process the file. Without that, the text needed for the update - \r, spaces, \rand a filename - will be appended to a buffer and only written to the OS - in e.g. 4k chunks - when the buffer is full, so the filename displayed would lag dozens of files behind the actual file being processing. Further, say the buffer is 4k - 4096 bytes - and at some point you have 4080 bytes buffered, then output text for the next filename: you'll end up with \rand 15 spaces fitting in the buffer, which when auto-flushed will end up wiping out the first 15 characters on the line on-screen and leaving the rest of the previous filename (if it was longer than 15 characters), then waiting until the buffer is full again before updating the screen (still haphazardly).

std::flush确保了C ++程序调用OSwrite()功能开始处理该文件之前发送文本到终端。没有它,更新所需的文本 - \r,空格\r和文件名 - 将被附加到缓冲区并且仅写入操作系统 - 例如以 4k 块 - 当缓冲区已满时,因此显示的文件名将滞后数十个文件在正在处理的实际文件后面。此外,假设缓冲区是 4k - 4096 字节 - 在某些时候你有 4080 字节缓冲,然后输出下一个文件名的文本:你最终会得到\r和缓冲区中的 15 个空格,当自动刷新时,它最终会清除屏幕上该行的前 15 个字符,并留下前一个文件名的其余部分(如果它长于 15 个字符),然后等待直到在更新屏幕之前缓冲区再次已满(仍然是随意的)。

The final std::endljust moves the cursor on from the line where you've been printing filenames so you can write "all done", or just leave main()and have the shell prompt display on a nice clean line, instead of potentially overwriting part of your last filename (great shells like zsh check for this).

最后std::endl只是将光标从您一直在打印文件名的行上移动,这样您就可以写下“全部完成”,或者只是离开main()并让 shell 提示显示在一个漂亮的干净行上,而不是可能覆盖您最后一个文件名的一部分(像 zsh 这样的伟大的 shell 检查这个)。

回答by Marco

std::cout interpretes "\r" as return to the beguining of the line, if you dont whant to be adding "<< endl" each time, use "\n"

std::cout 将 "\r" 解释为返回到行的开头,如果您不想每次都添加 "<< endl",请使用 "\n"

std::cout << "this will work!\nSee... a new line!" << std::endl;

std::cout << "this will work!\nSee... a new line!" << std::endl;