31

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.

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::cout to accomplish this?

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

3 Answers 3

38

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

Something like:

std::cout << "will not see this\rwill see this" << std::flush;
std::cout << std::endl; // all done
5
  • 1
    ... and Linux, and pretty much everything else. I think the only platform that one can still theoretically run into that won't handle this correctly is pre-X versions of Mac OS. Commented Jun 16, 2010 at 23:53
  • 4
    That will actually print ""will see thisthis"
    – Algoman
    Commented Aug 18, 2017 at 8:19
  • I mean, I would have also expected that to work, but then I put your very code in cpp.sh and it returned "will not see thiswill see this", see cpp.sh/3kgrm . Is this a mistake on the side of cpp.sh? Was thinking they use newest standard compilers.
    – Aziuth
    Commented Oct 24, 2018 at 10:11
  • 1
    Do not flush before endl as that includes an implicit flush. In fact, whenever you don't have to output immediately, don't use endl or flush at all. For line breaks, just use '\n'. The stream will flush at the latest when it is destroyed.
    – eike
    Commented Oct 31, 2019 at 13:18
  • @Aziuth: the problem is not the compiler, it's that cpp.sh's code to take the process standard output and render it in the browser doesn't support \r like a normal command-line terminal would. Commented May 15, 2020 at 21:02
2

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 just a carriage return.

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

If you're looking for in-place updates (e.g. overwrite the current line), I'd recommend looking at the curses lib. 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).

1
  • Mac used to treat CR differently from Win/Unix back in the day when it was not Unix itself. But now it is, and that problem is gone. Commented Jun 17, 2010 at 3:46
0

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

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

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.

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::flush ensures 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, \r and 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 \r and 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).

The final std::endl just 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).

Not the answer you're looking for? Browse other questions tagged or ask your own question.