【问题标题】:How do command line tools change their output after outputting it?命令行工具输出后如何改变其输出?
【发布时间】:2015-11-01 05:54:54
【问题描述】:

我注意到很多命令行工具(例如 wget)会将进度显示为数字或进度条,该进度条会随着进程的完成而前进。虽然这个问题并不是真正特定于语言的,但在我最常用于命令行工具的语言(C++、Node.js、Haskell)中,我还没有看到一种方法来做到这一点。

这里是一个例子,当 wget 下载一个文件时,终端的 单行 的三个快照:

与其他信息一起,wget 会显示一个进度条 (),它会在下载文件时前进。到目前为止下载的数据量(6363、179561、316053)和当前下载速度(10.7KB/s、65.8KB/s、63.0KB/s)也会更新。这是怎么做到的?

理想情况下,请包含来自上述三种语言中的一种或多种语言的代码示例。

【问题讨论】:

标签: c++ node.js haskell command-line terminal


【解决方案1】:

只需打印一个 CR(不带换行符)来覆盖一行。下面是 perl 中的示例程序:

#!/usr/bin/env perl

$| = 1;

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

我还禁用了输出缓冲 ($| = 1),以便打印命令立即将其输出发送到控制台而不是缓冲它。

Haskell 示例:

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

【讨论】:

    【解决方案2】:

    查看 GitHub 上的 GNU wget repo -- progress.c

    他们似乎以相同的方式执行此操作,即打印 \r 然后覆盖。

    /* 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);
    }
    

    【讨论】:

      【解决方案3】:

      我只能说 node.js,但是内置的 readline 模块内置了一些非常基本的屏幕处理功能。例如:

      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);
      

      如果你想更高级,也有第三方模块,例如multimeter/meterboxblessed/blessed-contrib

      一般来说,有些程序使用 ncurses,而其他程序只是手动输出 ANSI 转义码以清除和重绘当前行。

      【讨论】:

        【解决方案4】:

        他们可能使用花哨的ncurses 库,但在我的Linux 上,我只是发送'\r' 将光标移回行首,用新的进度信息覆盖它。

        #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;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-04
          • 2021-03-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多