Wie animiere ich die Kommandozeile?

80

Ich habe mich immer gefragt, wie Leute eine vorherige Zeile in einer Befehlszeile aktualisieren. Ein gutes Beispiel hierfür ist die Verwendung des Befehls wget unter Linux. Es wird eine Art ASCII-Ladeleiste erstellt, die folgendermaßen aussieht:

[======>] 37%

und natürlich bewegt sich die Ladeleiste und der Prozentsatz ändert sich, aber es wird keine neue Linie erstellt. Ich kann nicht herausfinden, wie das geht. Kann mich jemand in die richtige Richtung weisen?

The.Anti.9
quelle

Antworten:

46

Ich kenne zwei Möglichkeiten, dies zu tun:

  • Verwenden Sie das Escapezeichen für die Rücktaste ('\ b'), um Ihre Zeile zu löschen
  • Verwenden Sie das cursesPaket, wenn Ihre Programmiersprache Bindungen enthält.

Und ein Google enthüllte ANSI-Fluchtcodes , die ein guter Weg zu sein scheinen. Als Referenz ist hier eine Funktion in C ++, um dies zu tun:

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
quelle
7
Angenommen, er führt eine Win32-Konsolen-App (nicht DOS) unter einer neueren Windows-Version (dh 2000+) aus, funktionieren ANSI-Escape-Codes überhaupt nicht. Wie in dem Wikipedia-Artikel angegeben, auf den Sie verlinkt haben.
Hugh Allen
Sie können Ansicon verwenden, wenn Sie mit ANSI-Escape-Sequenzen unter Windows arbeiten. github.com/adoxa/ansicon
Jens A. Koch
58

Eine Möglichkeit, dies zu tun, besteht darin, die Textzeile wiederholt mit dem aktuellen Fortschritt zu aktualisieren. Zum Beispiel:

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

Beachten Sie, dass ich sys.stdout.writeanstelle von print(dies ist Python) verwendet habe, da printautomatisch "\ r \ n" (Wagenrücklauf-Neuzeile) am Ende jeder Zeile gedruckt wird. Ich möchte nur den Wagenrücklauf, der den Cursor an den Zeilenanfang zurückbringt. Auch das flush()ist notwendig , da standardmäßig sys.stdoutnur die Ausgabe nach einem Newline spült (oder nach dem Puffer voll ist ).

Greg Hewgill
quelle
Und das gleiche in 'c' mit printf und '\ r'.
David L Morris
@Nearoo Normalerweise puffert stdout seine Ausgabe, bis eine neue Zeile (\ n) geschrieben wird. Durch das Spülen wird die Teillinie sofort angezeigt.
Greg Hewgill
20

Das Geheimnis besteht darin, nur \ r anstelle von \ n oder \ r \ n am und der Zeile zu drucken.

\ r heißt Wagenrücklauf und bewegt den Cursor am Zeilenanfang

\ n heißt Zeilenvorschub und bewegt den Cursor in der nächsten Zeile der Konsole. Wenn Sie nur \ r verwenden, überschreiben Sie die zuvor geschriebene Zeile. Schreiben Sie also zuerst eine Zeile wie die folgende:

[          ]

Fügen Sie dann für jedes Häkchen ein Zeichen hinzu

\r[=         ]

\r[==        ]

...

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

und so weiter. Sie können 10 Zeichen verwenden, die jeweils 10% darstellen. Wenn Sie nach Abschluss eine Meldung anzeigen möchten, vergessen Sie nicht, auch genügend weiße Zeichen hinzuzufügen, damit Sie die zuvor geschriebenen Gleichheitszeichen wie folgt überschreiben:

\r[done      ]
icenac
quelle
1
Das hat komplett funktioniert. Meiner Meinung nach ist es auch VIEL einfacher.
Erutan409
4

Unten ist meine Antwort, verwenden Sie die Windows API Consoles (Windows) , Codierung von 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
quelle
Wo ist bInfodefiniert?
Tomáš Zato - Wiedereinsetzung Monica
3

PowerShell verfügt über ein Cmdlet "Write-Progress", mit dem eine Fortschrittsanzeige in der Konsole erstellt wird, die Sie während der Ausführung Ihres Skripts aktualisieren und ändern können.

Steven Murawski
quelle
3

Hier ist die Antwort auf Ihre Frage ... (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
quelle
2

Im Anschluss an Gregs Antwort finden Sie hier eine erweiterte Version seiner Funktion, mit der Sie mehrzeilige Nachrichten anzeigen können. Übergeben Sie einfach eine Liste oder ein Tupel der Zeichenfolgen, die Sie anzeigen / aktualisieren möchten.

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

Hinweis: Ich habe dies nur mit einem Linux-Terminal getestet, daher kann Ihr Kilometerstand auf Windows-basierten Systemen variieren.

Blaker
quelle
@naxa Funktioniert Gregs Antwort (oben) für Sie? Es ist höchstwahrscheinlich ein Problem mit dem Zeilenumbruch. Versuchen Sie, '\ n' durch '\ r \ n' zu ersetzen.
Blaker
Gregs funktionieren, also funktioniert es in einer Zeile, aber ich habe versucht, mehrzeilige Nachrichtenaktualisierungen durchzuführen. :) Ich habe ersetzt , \num \r\nin Ihrem Skript, konnte aber immer noch nicht auf Windows bekommen arbeiten (Sie tun?). Ich habe ←[A←[Anach einigen Nachrichten, ich vermute, dass die '\x1b[A'Sequenz nicht das tut, was sie sollte cmd.exe.
n611x007
1
@naxa Das '\ x1b [A' ist eine ANSI-Escape-Sequenz für den Cursor nach oben, mit der der Cursor auf den Anfang des Zeilenblocks in meinem Code zurückgesetzt wird. Ich habe mir das etwas genauer angesehen und festgestellt, dass die Win32-Konsole ANSI-Escape-Sequenzen überhaupt nicht unterstützt . Möglicherweise möchten Sie meiner Funktion eine if-Anweisung hinzufügen, um die hier erwähnte Lösung zum Hinzufügen von ANSI-Unterstützung zu stdout unter Windows zu verpacken .
Blaker
0

Wenn Sie eine Skriptsprache verwenden, können Sie den Befehl "tput cup" verwenden, um dies zu erledigen ... PS Dies ist nur eine Linux / Unix-Sache, soweit ich weiß ...

Justin
quelle