Jak animować wiersz poleceń?

80

Zawsze się zastanawiałem, jak ludzie aktualizują poprzednią linię w linii poleceń. świetnym tego przykładem jest użycie polecenia wget w systemie Linux. Tworzy pasek ładowania ASCII, który wygląda następująco:

[======>] 37%

i oczywiście pasek ładowania przesuwa się i zmienia się procent, ale nie tworzy nowej linii. Nie wiem, jak to zrobić. Czy ktoś może wskazać mi właściwy kierunek?

The.Anti.9
źródło

Odpowiedzi:

46

Znam dwa sposoby, aby to zrobić:

  • Użyj znaku ucieczki Backspace („\ b”), aby usunąć linię
  • Użyj cursespakietu, jeśli wybrany język programowania ma dla niego powiązania.

A Google ujawnił kody ucieczki ANSI , które wydają się być dobrym sposobem. Dla porównania, oto funkcja w C ++ służąca do tego:

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.
}
hazzen
źródło
7
Zakładając, że uruchamia aplikację konsolową Win32 (nie DOS) na najnowszej wersji systemu Windows (tj. 2000+), kody ucieczki ANSI w ogóle nie będą działać. Jak stwierdzono w artykule na Wikipedii, do którego utworzyłeś link.
Hugh Allen,
Możesz użyć Ansicon, jeśli pracujesz z sekwencjami ucieczki ANSI w systemie Windows. github.com/adoxa/ansicon
Jens A. Koch
58

Jednym ze sposobów jest wielokrotne aktualizowanie wiersza tekstu o bieżące postępy. Na przykład:

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

Zauważ, że użyłem sys.stdout.writezamiast print(to jest Python), ponieważ printautomatycznie drukuje "\ r \ n" (nowa linia powrotu karetki) na końcu każdej linii. Chcę tylko powrotu karetki, który przywraca kursor na początek wiersza. Jest również flush()konieczne, ponieważ domyślnie sys.stdoutopróżnia swoje wyjście tylko po nowej linii (lub po zapełnieniu bufora).

Greg Hewgill
źródło
I to samo w „c” z printf i „\ r”.
David L Morris,
@Nearoo Normalnie stdout buforuje swoje dane wyjściowe do momentu zapisania nowej linii (\ n). Płukanie powoduje natychmiastowe pojawienie się częściowej linii.
Greg Hewgill
21

Sekret polega na wypisaniu tylko \ r zamiast \ n lub \ r \ n na końcu wiersza.

\ r nazywa się powrotem karetki i przenosi kursor na początek wiersza

\ n nazywa się przesuwem o wiersz i przenosi kursor do następnego wiersza w konsoli. Jeśli używasz tylko \ r, nadpiszesz poprzednio napisaną linię. Więc najpierw napisz linię taką jak ta:

[          ]

następnie dodaj znak dla każdego kleszcza

\r[=         ]

\r[==        ]

...

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

i tak dalej. Możesz użyć 10 znaków, z których każdy reprezentuje 10%. Ponadto, jeśli chcesz wyświetlić komunikat po zakończeniu, nie zapomnij również dodać wystarczającej liczby białych znaków, aby nadpisać wcześniej zapisane znaki równości w następujący sposób:

\r[done      ]
icenac
źródło
1
To zadziałało całkowicie. Moim zdaniem jest to DUŻO prostsze.
Erutan409
4

poniżej jest moja odpowiedź, użyj konsoli Windows API (Windows) , kodowanie C.

/*
* file: ProgressBarConsole.cpp
* description: a console progress bar Demo
* author: lijian <[email protected]>
* 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;
}
hustljian
źródło
Gdzie jest bInfozdefiniowane?
Tomáš Zato - Przywróć Monikę
3

Program PowerShell zawiera polecenie cmdlet Write-Progress, które tworzy w konsoli pasek postępu, który można aktualizować i modyfikować podczas uruchamiania skryptu.

Steven Murawski
źródło
3

Oto odpowiedź na twoje pytanie ... (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")
naren
źródło
2

W następstwie odpowiedzi Grega , oto rozszerzona wersja jego funkcji, która umożliwia wyświetlanie wiadomości wielowierszowych; po prostu podaj listę lub krotkę ciągów, które chcesz wyświetlić / odświeżyć.

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

Uwaga: przetestowałem to tylko przy użyciu terminala linux, więc Twój przebieg może się różnić w systemach opartych na systemie Windows.

Blaker
źródło
@naxa Czy odpowiedź Grega (powyżej) działa dla Ciebie? Najprawdopodobniej jest to problem ze znakiem nowej linii. Spróbuj zamienić „\ n” na „\ r \ n”.
Blaker
Greg działa, więc w jednej linii to działa, ale próbowałem aktualizować wiadomości w wielu wierszach. :) Zamieniłem \nna \r\nw twoim skrypcie, ale nadal nie mogłem go uruchomić na Windowsie (prawda?). Ja się ←[A←[Apo kilku wiadomościach, Podejrzewam, że '\x1b[A'kolejność nie robić to, co powinno się cmd.exe.
n611x007
1
@naxa '\ x1b [A' to sekwencja ucieczki ANSI dla kursora w górę, która służy do resetowania kursora do początku bloku wierszy w moim kodzie. Przyjrzałem się temu nieco dokładniej i stwierdziłem, że konsola Win32 w ogóle nie obsługuje sekwencji ucieczki ANSI . Możesz chcieć dodać instrukcję if do mojej funkcji, aby opakować rozwiązanie wymienione tutaj w celu dodania obsługi ANSI do standardowego wyjścia w systemie Windows.
Blaker,
0

Jeśli używasz języka skryptowego, możesz użyć polecenia "tput cup", aby to zrobić ... PS To jest kwestia Linuksa / Uniksa, o ile wiem ...

Justin
źródło