2010-01-23 31 views
27

Actualmente estoy tratando de agregar una barra de progreso a un script de línea de comando y he intentado varias soluciones (incluyendo Zend y Console_ProgressBar). El problema que ambos tienen en común es que la barra de progreso no se pega en la parte inferior de la ventana porque durante la secuencia de comandos se generan nuevas líneas y otra información.Barra de progreso de línea de comando en PHP

¿Hay alguna manera de mantener la barra de progreso en la parte inferior del terminal pero aún así poder mostrar otra información mientras se ejecuta el script?

[Editar]

lo he descubierto:

En lugar de dar salida directamente a la salida estándar en realidad estoy agarrando la salida dentro de una variable, borro la pantalla con echo chr(27) . '[2J' y luego la salida estándar el contenido de la variable y luego agregar mi barra de progreso.

esperanza que tiene sentido :)

+0

Me acaba de lanzar mi progressbar: https://github.com/Ex3v/PHP-ProgressBar – ex3v

Respuesta

64

Esto es bueno para la barra de progreso de la CLI:

http://snipplr.com/view/29548/

<?php 

/* 

Copyright (c) 2010, dealnews.com, Inc. 
All rights reserved. 

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met: 

* Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer. 
* Redistributions in binary form must reproduce the above copyright 
    notice, this list of conditions and the following disclaimer in the 
    documentation and/or other materials provided with the distribution. 
* Neither the name of dealnews.com, Inc. nor the names of its contributors 
    may be used to endorse or promote products derived from this software 
    without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE. 

*/ 

/** 
* show a status bar in the console 
* 
* <code> 
* for($x=1;$x<=100;$x++){ 
* 
*  show_status($x, 100); 
* 
*  usleep(100000); 
*       
* } 
* </code> 
* 
* @param int  $done how many items are completed 
* @param int  $total how many items are to be done total 
* @param int  $size optional size of the status bar 
* @return void 
* 
*/ 

function show_status($done, $total, $size=30) { 

    static $start_time; 

    // if we go over our bound, just ignore it 
    if($done > $total) return; 

    if(empty($start_time)) $start_time=time(); 
    $now = time(); 

    $perc=(double)($done/$total); 

    $bar=floor($perc*$size); 

    $status_bar="\r["; 
    $status_bar.=str_repeat("=", $bar); 
    if($bar<$size){ 
     $status_bar.=">"; 
     $status_bar.=str_repeat(" ", $size-$bar); 
    } else { 
     $status_bar.="="; 
    } 

    $disp=number_format($perc*100, 0); 

    $status_bar.="] $disp% $done/$total"; 

    $rate = ($now-$start_time)/$done; 
    $left = $total - $done; 
    $eta = round($rate * $left, 2); 

    $elapsed = $now - $start_time; 

    $status_bar.= " remaining: ".number_format($eta)." sec. elapsed: ".number_format($elapsed)." sec."; 

    echo "$status_bar "; 

    flush(); 

    // when done, send a newline 
    if($done == $total) { 
     echo "\n"; 
    } 

} 

?> 
+2

Esto es por lo que la mejor respuesta –

+1

Mucho. A modo de resumen, la parte importante de ese fragmento de código emite un eco de '" \ r "' (se requieren comillas dobles) antes de escribir la nueva línea. –

+0

Peor cosa acerca de esa función es que normalmente se inicia un bucle for con $ c = 0, no $ c = 1. Como comienza con $ c = 0, obtiene una excepción de División por Cero con la función. –

3

Ésta es una mejora de la respuesta anterior que maneja cambia el tamaño de terminal y usa 2 líneas en lugar de 1. La primera línea es información como el tiempo/porcentaje de la segunda línea es la barra de progreso.

<? 
/* 

Copyright (c) 2010, dealnews.com, Inc. 
All rights reserved. 

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met: 

* Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer. 
* Redistributions in binary form must reproduce the above copyright 
    notice, this list of conditions and the following disclaimer in the 
    documentation and/or other materials provided with the distribution. 
* Neither the name of dealnews.com, Inc. nor the names of its contributors 
    may be used to endorse or promote products derived from this software 
    without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE. 

*/ 

/** 
* show a status bar in the console 
* 
* <code> 
* for($x=1;$x<=100;$x++){ 
* 
*  show_status($x, 100); 
* 
*  usleep(100000); 
* 
* } 
* </code> 
* 
* @param int  $done how many items are completed 
* @param int  $total how many items are to be done total 
* @param int  $size optional size of the status bar 
* @return void 
* 
*/ 

function show_status($done, $total, $size=30, $lineWidth=-1) { 
    if($lineWidth <= 0){ 
     $lineWidth = $_ENV['COLUMNS']; 
    } 

    static $start_time; 

    // to take account for [ and ] 
    $size -= 3; 
    // if we go over our bound, just ignore it 
    if($done > $total) return; 

    if(empty($start_time)) $start_time=time(); 
    $now = time(); 

    $perc=(double)($done/$total); 

    $bar=floor($perc*$size); 

    // jump to the begining 
    echo "\r"; 
    // jump a line up 
    echo "\x1b[A"; 

    $status_bar="["; 
    $status_bar.=str_repeat("=", $bar); 
    if($bar<$size){ 
     $status_bar.=">"; 
     $status_bar.=str_repeat(" ", $size-$bar); 
    } else { 
     $status_bar.="="; 
    } 

    $disp=number_format($perc*100, 0); 

    $status_bar.="]"; 
    $details = "$disp% $done/$total"; 

    $rate = ($now-$start_time)/$done; 
    $left = $total - $done; 
    $eta = round($rate * $left, 2); 

    $elapsed = $now - $start_time; 


    $details .= " " . formatTime($eta)." ". formatTime($elapsed); 

    $lineWidth--; 
    if(strlen($details) >= $lineWidth){ 
     $details = substr($details, 0, $lineWidth-1); 
    } 
    echo "$details\n$status_bar"; 

    flush(); 

    // when done, send a newline 
    if($done == $total) { 
     echo "\n"; 
    } 

} 

No sé por qué el código anterior tiene una licencia Estoy copiarlo para estar seguro. El código de abajo no tiene licencia. Libre de usar para cualquier propósito.

function formatTime($sec){ 
    if($sec > 100){ 
     $sec /= 60; 
     if($sec > 100){ 
      $sec /= 60; 
      return number_format($sec) . " hr"; 
     } 
     return number_format($sec) . " min"; 
    } 
    return number_format($sec) . " sec"; 
} 


class Timer { 
    public $time; 
    function __construct(){ 
     $this->start(); 
    } 
    function start($offset=0){ 
     $this->time = microtime(true) + $offset; 
    } 
    function seconds(){ 
     return microtime(true) - $this->time; 
    } 
}; 


// We need this to limit the frequency of the progress bar. Or else it 
// hugely slows down the app. 
class FPSLimit { 
    public $frequency; 
    public $maxDt; 
    public $timer; 
    function __construct($freq){ 
     $this->setFrequency($freq); 
     $this->timer = new Timer(); 
     $this->timer->start(); 
    } 
    function setFrequency($freq){ 
     $this->frequency = $freq; 
     $this->maxDt = 1.0/$freq; 
    } 
    function frame(){ 
     $dt = $this->timer->seconds(); 
     if($dt > $this->maxDt){ 
      $this->timer->start($dt - $this->maxDt); 
      return true; 
     } 
     return false; 
    } 
}; 

class Progress { 
    // generic progress class to update different things 
    function update($units, $total){} 
} 

class SimpleProgress extends Progress { 
    private $cols; 
    private $limiter; 
    private $units; 
    private $total; 

    function __construct(){ 
     // change the fps limit as needed 
     $this->limiter = new FPSLimit(10); 
     echo "\n"; 
    } 

    function __destruct(){ 
     $this->draw(); 
    } 

    function updateSize(){ 
     // get the number of columns 
     $this->cols = exec("tput cols"); 
    } 

    function draw(){ 
     $this->updateSize(); 
     show_status($this->units, $this->total, $this->cols, $this->cols); 
    } 

    function update($units, $total){ 
     $this->units = $units; 
     $this->total = $total; 
     if(!$this->limiter->frame()) 
      return; 
     $this->draw(); 
    } 
} 


// example 

$tasks = rand() % 700 + 600; 
$done = 0; 

$progress = new SimpleProgress(); 

for($done = 0; $done <= $tasks; $done++){ 
    usleep((rand() % 127)*100); 
    $progress->update($done, $tasks); 
} 
+0

Gran trabajo. Rendimiento alucinante –

+0

¡Brillante! El bit '\ x1b [A' es justo lo que necesitaba para tener la barra de progreso multilínea :) – robinmitra

31

Otras respuestas parecen demasiado complejas. Mi solución es simplemente hacer eco \ 033 [0G secuencia de escape antes de la próxima actualización y mueve el cursor de regreso al principio.

function progressBar($done, $total) { 
    $perc = floor(($done/$total) * 100); 
    $left = 100 - $perc; 
    $write = sprintf("\033[0G\033[2K[%'={$perc}s>%-{$left}s] - $perc%% - $done/$total", "", ""); 
    fwrite(STDERR, $write); 
} 

Llamar a la función por primera vez da salida a la barra de progreso y cada llamada posterior sobrescribe la última línea con una nueva barra de progreso.

EDIT: He cambiado echoing \ r a la secuencia de escape \ 033 [0G y esto ahora debería funcionar en OSX así como en Linux/Unix.

EDIT 2: Se corrigió el posible error en la línea 3 según la sugerencia de @tbjers.

Datos 3: Se ha actualizado con una nueva versión que imprime a STDERR y ahora también en GitHub: https://github.com/MacroMan/PHPTerminalProgressBar

+0

Intenté esto, pero se siguen agregando nuevas líneas. Probado en Mac PHP CLI – Wouter

+0

osx usa \ r como retorno de carro y avance de línea, por lo que esto no funcionará en osx. – MacroMan

+1

La línea 3 debe cambiarse a lo siguiente para evitar las advertencias de 'str_repeat':' $ bar = "[". ($ perc> 0? str_repeat ("=", $ perc - 1): ''). ">"; ' – tbjers