¿Cómo animar la línea de comandos?

80

Siempre me he preguntado cómo la gente actualiza una línea anterior en una línea de comando. un gran ejemplo de esto es cuando se usa el comando wget en linux. Crea una especie de barra de carga ASCII que se ve así:

[======>] 37%

y, por supuesto, la barra de carga se mueve y el porcentaje cambia, pero no hace una nueva línea. No puedo encontrar la manera de hacer esto. ¿Alguien me puede apuntar en la dirección correcta?

El.Anti.9
fuente

Respuestas:

46

Hay dos formas que conozco de hacer esto:

  • Utilice el carácter de escape de retroceso ('\ b') para borrar su línea
  • Utilice el cursespaquete, si el lenguaje de programación de su elección tiene enlaces para él.

Y Google reveló códigos de escape ANSI , que parecen ser una buena forma. Como referencia, aquí hay una función en C ++ para hacer esto:

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
fuente
7
Suponiendo que esté ejecutando una aplicación de consola Win32 (no DOS) en una versión reciente de Windows (es decir, 2000+), los códigos de escape ANSI no funcionarán en absoluto. Como se indica en el artículo de wikipedia al que se vinculó.
Hugh Allen
Puede usar Ansicon, si trabaja con ANSI Escape Sequences en Windows. github.com/adoxa/ansicon
Jens A. Koch
58

Una forma de hacerlo es actualizar repetidamente la línea de texto con el progreso actual. Por ejemplo:

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

Tenga en cuenta que utilicé en sys.stdout.writelugar de print(esto es Python) porque printimprime automáticamente "\ r \ n" (nueva línea de retorno de carro) al final de cada línea. Solo quiero el retorno de carro que devuelve el cursor al inicio de la línea. Además, flush()es necesario porque, de forma predeterminada, sys.stdoutsolo vacía su salida después de una nueva línea (o después de que su búfer se llena).

Greg Hewgill
fuente
Y lo mismo en 'c' con printf y '\ r'.
David L Morris
@Nearoo Normalmente, stdout almacena en búfer su salida hasta que se escribe una nueva línea (\ n). El enjuague hace que la línea parcial aparezca inmediatamente.
Greg Hewgill
20

El secreto es imprimir solo \ r en lugar de \ n o \ r \ n en la línea y.

\ r se llama retorno de carro y mueve el cursor al comienzo de la línea

\ n se llama salto de línea y mueve el cursor a la siguiente línea en la consola. Si solo usa \ r, sobrescribe la línea escrita anteriormente. Así que primero escribe una línea como la siguiente:

[          ]

luego agregue un signo para cada tick

\r[=         ]

\r[==        ]

...

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

y así. Puede utilizar 10 caracteres, cada uno de los cuales representa un 10%. Además, si desea mostrar un mensaje cuando haya terminado, no olvide agregar también suficientes caracteres en blanco para que sobrescriba los signos iguales escritos anteriormente de esta manera:

\r[done      ]
icenac
fuente
1
Esto funcionó, completamente. En mi opinión, también es MUCHO más simple.
Erutan409
4

a continuación está mi respuesta, use las consolas API de Windows (Windows) , codificación de 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
fuente
¿Dónde se bInfodefine?
Tomáš Zato - Reincorpora a Monica el
3

PowerShell tiene un cmdlet Write-Progress que crea una barra de progreso en la consola que puede actualizar y modificar a medida que se ejecuta su script.

Steven Murawski
fuente
3

Aquí está la respuesta a su pregunta ... (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
fuente
2

Como continuación de la respuesta de Greg , aquí hay una versión extendida de su función que le permite mostrar mensajes de varias líneas; simplemente pase una lista o tupla de las cadenas que desea mostrar / actualizar.

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

Nota: Solo he probado esto usando una terminal Linux, por lo que su kilometraje puede variar en los sistemas basados ​​en Windows.

Blaker
fuente
@naxa ¿La respuesta de Greg (arriba) funciona para usted? Lo más probable es que sea un problema con el carácter de nueva línea. Intente reemplazar '\ n' con '\ r \ n'.
Blaker
Greg funciona, así que en una línea funciona, pero intenté hacer actualizaciones de mensajes de varias líneas. :) He reemplazado \na \r\nen su secuencia de comandos, pero todavía no puedo hacerlo funcionar en Windows (¿verdad?) Recibo ←[A←[Aalgunos mensajes, sospecho que la '\x1b[A'secuencia no hace lo que debería cmd.exe.
n611x007
1
@naxa La '\ x1b [A' es una secuencia de escape ANSI para el cursor hacia arriba, que se usa para restablecer el cursor al inicio del bloque de líneas en mi código. Investigué esto un poco más y descubrí que la consola Win32 no admite secuencias de escape ANSI en absoluto . Es posible que desee agregar una declaración if a mi función para envolver la solución mencionada aquí para agregar soporte ANSI a stdout en Windows.
Blaker
0

Si está usando un lenguaje de scripting, podría usar el comando "tput cup" para hacer esto ... PD: Esto es algo de Linux / Unix solo que yo sepa ...

Justin
fuente