Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do command line tools change their output after outputting it?

I've noticed that a lot of command line tools, wget for example, will show progress as a number or progress bar that advances as a process is completed. While the question isn't really language-specific, out of the languages I use most often for command line tools (C++, Node.js, Haskell) I haven't seen a way to do this.

Here's an example, three snapshots of a single line of Terminal as wget downloads a file:Beginning processMiddle of processProcess nearly complete

Along with other information, wget shows a progress bar (<=>) that advances as it downloads a file. The amount of data downloaded so far (6363, 179561, 316053) and the current download speed (10.7KB/s, 65.8KB/s, 63.0KB/s) update as well. How is this done?

Ideally, please include a code sample from one or more of the three languages mentioned above.

like image 613
TheEnvironmentalist Avatar asked Aug 09 '15 03:08

TheEnvironmentalist


4 Answers

Just print a CR (without a newline) to overwrite a line. Here is an example program in perl:

#!/usr/bin/env perl

$| = 1;

for (1..10) {
  print "the count is: $_\r";
  sleep(1)
}

I've also disabled output buffering ($| = 1) so that the print command sends its output to the console immediately instead of buffering it.

Haskell example:

import System.IO
import Control.Monad
import Control.Concurrent

main = do
  hSetBuffering stdout NoBuffering
  forM_ [1..10] $ \i -> do
    putStr $ "the count is: " ++ show i ++ "\r"
    threadDelay 1000000
like image 104
ErikR Avatar answered Oct 19 '22 07:10

ErikR


Looking at GNU wget repo on GitHub -- progress.c

It seems they do it the same way i.e. print a \r and then overwrite.

/* Print the contents of the buffer as a one-line ASCII "image" so
   that it can be overwritten next time.  */

static void
display_image (char *buf)
{
  bool old = log_set_save_context (false);
  logputs (LOG_VERBOSE, "\r");
  logputs (LOG_VERBOSE, buf);
  log_set_save_context (old);
}
like image 28
aa333 Avatar answered Oct 19 '22 06:10

aa333


I can only speak about node.js, but the built-in readline module has some very basic screen handling functionality built-in. For example:

var readline = require('readline');
var c = 0;
var intvl = setInterval(function() {
  // Clear entirety of current line
  readline.clearLine(process.stdout, 0);
  readline.cursorTo(process.stdout, 0);
  process.stdout.write('Progress: ' + (++c) + '%');
  if (c === 100)
    clearInterval(intvl);
}, 500);

There are also third party modules if you want to get fancier, such as multimeter/meterbox and blessed/blessed-contrib.

Generally speaking though, some programs use ncurses, while others simply just manually output the ANSI escape codes to clear and redraw the current line.

like image 3
mscdex Avatar answered Oct 19 '22 06:10

mscdex


They probably use the fancy ncurses library but on my Linux for my personal command-line tools I simply send '\r' to move the cursor back to the start of the line to overwrite it with new progress information.

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

int main()
{
    for(auto i = 0; i < 100; ++i)
    {
        std::cout << "\rprogress: " << i << "%        " << std::flush;
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    std::cout << "\rprogress: DONE             " << std::flush;
}
like image 1
Galik Avatar answered Oct 19 '22 06:10

Galik