Làm thế nào để tạo hiệu ứng cho dòng lệnh?


80

Tôi đã luôn tự hỏi làm thế nào mọi người cập nhật một dòng trước đó trong một dòng lệnh. một ví dụ tuyệt vời về điều này là khi sử dụng lệnh wget trong linux. Nó tạo ra một thanh tải ASCII có dạng như sau:

[======>] 37%

và tất nhiên thanh tải di chuyển và phần trăm thay đổi, Nhưng nó không tạo ra một dòng mới. Tôi không thể tìm ra cách để làm điều này. Ai đó có thể chỉ cho tôi đi đúng hướng?

Câu trả lời:


46

Có hai cách tôi biết để làm điều này:

  • Sử dụng ký tự thoát backspace ('\ b') để xóa dòng của bạn
  • Sử dụng cursesgói, nếu ngôn ngữ lập trình bạn chọn có các ràng buộc cho nó.

Và Google đã tiết lộ Mã thoát ANSI , có vẻ là một cách tốt. Để tham khảo, đây là một hàm trong C ++ để thực hiện việc này:

void DrawProgressBar(int len, double percent) {
  cout << "\x1B[2K"; // Erase the entire current line.
  cout << "\x1B[0E"; // Move to the beginning of the current line.
  string progress;
  for (int i = 0; i < len; ++i) {
    if (i < static_cast<int>(len * percent)) {
      progress += "=";
    } else {
      progress += " ";
    }
  }
  cout << "[" << progress << "] " << (static_cast<int>(100 * percent)) << "%";
  flush(cout); // Required.
}

7
Giả sử anh ta đang chạy ứng dụng bảng điều khiển Win32 (không phải DOS) trên phiên bản Windows gần đây (tức là 2000+), thì mã thoát ANSI sẽ không hoạt động. Như đã nêu trong bài viết wikipedia mà bạn đã liên kết đến.
Hugh Allen

Bạn có thể sử dụng Acularon, nếu làm việc với ANSI Escape Sequences trên Windows. github.com/adoxa/ansicon
Jens A. Koch

58

Một cách để làm điều này là liên tục cập nhật dòng văn bản với tiến độ hiện tại. Ví dụ:

def status(percent):
    sys.stdout.write("%3d%%\r" % percent)
    sys.stdout.flush()

Lưu ý rằng tôi đã sử dụng sys.stdout.writethay vì print(đây là Python) vì printtự động in "\ r \ n" (ký tự xuống dòng mới) ở cuối mỗi dòng. Tôi chỉ muốn ký tự xuống dòng trả lại con trỏ về đầu dòng. Ngoài ra, điều flush()này là cần thiết vì theo mặc định, sys.stdoutchỉ xóa đầu ra của nó sau một dòng mới (hoặc sau khi bộ đệm của nó đầy).


Và tương tự trong 'c' với printf và '\ r'.
David L Morris

@Nearoo Thông thường stdout đệm đầu ra của nó cho đến khi dòng mới (\ n) được viết. Xả làm cho đường một phần xuất hiện ngay lập tức.
Greg Hewgill

20

Bí quyết là chỉ in \ r thay vì \ n hoặc \ r \ n tại và của dòng.

\ r được gọi là ký tự xuống dòng và nó di chuyển con trỏ ở đầu dòng

\ n được gọi là nguồn cấp dữ liệu dòng và nó di chuyển con trỏ trên dòng tiếp theo Trong bảng điều khiển. Nếu bạn chỉ sử dụng \ r, bạn sẽ ghi đè lên dòng đã viết trước đó. Vì vậy, trước tiên hãy viết một dòng như sau:

[          ]

sau đó thêm một dấu hiệu cho mỗi lần đánh dấu

\r[=         ]

\r[==        ]

...

\r[==========]

và như thế. Bạn có thể sử dụng 10 ký tự, mỗi ký tự đại diện cho 10%. Ngoài ra, nếu bạn muốn hiển thị một thông báo khi hoàn thành, đừng quên thêm đủ ký tự trắng để bạn ghi đè lên các dấu bằng đã viết trước đó như sau:

\r[done      ]

1
Điều này đã hoạt động, hoàn toàn. Theo tôi, nó cũng đơn giản hơn rất nhiều.
Erutan409

4

dưới đây là câu trả lời của tôi, sử dụng Windows API Consoles (Windows) , mã hóa C.

/*
* file: ProgressBarConsole.cpp
* description: a console progress bar Demo
* author: lijian <hustlijian@gmail.com>
* version: 1.0
* date: 2012-12-06
*/
#include <stdio.h>
#include <windows.h>

HANDLE hOut;
CONSOLE_SCREEN_BUFFER_INFO bInfo;
char charProgress[80] = 
    {"================================================================"};
char spaceProgress = ' ';

/*
* show a progress in the [row] line
* row start from 0 to the end
*/
int ProgressBar(char *task, int row, int progress)
{
    char str[100];
    int len, barLen,progressLen;
    COORD crStart, crCurr;
    GetConsoleScreenBufferInfo(hOut, &bInfo);
    crCurr = bInfo.dwCursorPosition; //the old position
    len = bInfo.dwMaximumWindowSize.X;
    barLen = len - 17;//minus the extra char
    progressLen = (int)((progress/100.0)*barLen);
    crStart.X = 0;
    crStart.Y = row;

    sprintf(str,"%-10s[%-.*s>%*c]%3d%%", task,progressLen,charProgress, barLen-progressLen,spaceProgress,50);
#if 0 //use stdand libary
    SetConsoleCursorPosition(hOut, crStart);
    printf("%s\n", str);
#else
    WriteConsoleOutputCharacter(hOut, str, len,crStart,NULL);
#endif
    SetConsoleCursorPosition(hOut, crCurr);
    return 0;
}
int main(int argc, char* argv[])
{
    int i;
    hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleScreenBufferInfo(hOut, &bInfo);

    for (i=0;i<100;i++)
    {
        ProgressBar("test", 0, i);
        Sleep(50);
    }

    return 0;
}

Được bInfoxác định ở đâu?
Tomáš Zato - Phục hồi Monica

3

PowerShell có một lệnh ghép ngắn Write-Progress để tạo một thanh tiến trình trong bảng điều khiển mà bạn có thể cập nhật và sửa đổi khi tập lệnh của bạn chạy.


3

Đây là câu trả lời cho câu hỏi của bạn ... (python)

def disp_status(timelapse, timeout):
  if timelapse and timeout:
     percent = 100 * (float(timelapse)/float(timeout))
     sys.stdout.write("progress : ["+"*"*int(percent)+" "*(100-int(percent-1))+"]"+str(percent)+" %")
     sys.stdout.flush()
     stdout.write("\r  \r")

2

Tiếp theo câu trả lời của Greg , đây là một phiên bản mở rộng của chức năng cho phép bạn hiển thị các thông báo nhiều dòng; chỉ cần chuyển vào một danh sách hoặc nhiều chuỗi mà bạn muốn hiển thị / làm mới.

def status(msgs):
    assert isinstance(msgs, (list, tuple))

    sys.stdout.write(''.join(msg + '\n' for msg in msgs[:-1]) + msgs[-1] + ('\x1b[A' * (len(msgs) - 1)) + '\r')
    sys.stdout.flush()

Lưu ý: Tôi chỉ mới thử nghiệm điều này bằng cách sử dụng thiết bị đầu cuối linux, vì vậy số dặm của bạn có thể khác nhau trên các hệ thống chạy Windows.


@naxa Câu trả lời của Greg (ở trên) có phù hợp với bạn không? Rất có thể đó là sự cố với ký tự dòng mới. Hãy thử thay thế '\ n' bằng '\ r \ n'.
Blaker

Greg đang làm việc, vì vậy nó hoạt động trên một dòng, nhưng tôi đã cố gắng cập nhật tin nhắn nhiều dòng. :) Tôi đã thay thế \nthành \r\ntrong tập lệnh của bạn, nhưng vẫn không thể làm cho nó hoạt động trên windows (bạn phải không?). Tôi nhận được ←[A←[Asau một số tin nhắn, tôi nghi ngờ '\x1b[A'trình tự không làm như những gì nó cần cmd.exe.
n611x007

1
@naxa '\ x1b [A' là một chuỗi thoát ANSI cho con trỏ lên, được sử dụng để đặt lại con trỏ về đầu khối dòng trong mã của tôi. Tôi đã xem xét vấn đề này thêm một chút và thấy rằng bảng điều khiển Win32 không hỗ trợ trình tự thoát ANSI . Bạn có thể muốn thêm câu lệnh if vào hàm của tôi để kết thúc giải pháp được đề cập ở đây để thêm hỗ trợ ANSI cho stdout trên Windows.
Blaker

0

Nếu bạn sử dụng ngôn ngữ kịch bản, bạn có thể sử dụng lệnh "tput cup" để hoàn thành việc này ... PS Đây là một thứ chỉ Linux / Unix theo như tôi biết ...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.