Command line progress bar in PHP
我目前正在尝试将进度条添加到命令行脚本中,并尝试了各种解决方案(包括Zend和控制台进度条)。它们都有一个共同的问题,那就是进度条不会停留在窗口的底部,因为在脚本期间,会输出新的行和其他信息。
是否有任何方法可以将进度条保持在终端的底部,但在脚本运行时仍然能够输出其他信息?
[编辑]
我想出来了:
我不是直接输出到stdout,而是在变量内部获取输出,而是用
希望这是有道理的:)
这是一个很好的CLI进度条:
http://snipplr.com/view/29548/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | <?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 * * <wyn> * for($x=1;$x<=100;$x++){ * * show_status($x, 100); * * usleep(100000); * * } * </wyn> * * @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=" ["; $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" "; } } ?> |
其他答案似乎过于复杂。我的解决方案是在下一次更新之前简单地回显 33[0g转义序列,并将光标移回开始位置。
1 2 3 4 5 6 |
第一次调用函数将输出进度条,随后的每次调用都会用新的进度条覆盖最后一行。
编辑:我已经将echoting
改为escape sequence 33[0g,现在应该可以在OSX和Linux/Unix上使用了。
编辑2:根据@tbjers建议,修复了第3行可能出现的错误。
编辑3:更新了打印到stderr的新版本,现在也在github上:https://github.com/macroman/phpterminalprogressbar
这是前一个答案的改进,它处理终端的大小调整,使用2行而不是1行。第一行是时间/百分比信息,第二行是进度条。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | <? /* 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 * * <wyn> * for($x=1;$x<=100;$x++){ * * show_status($x, 100); * * usleep(100000); * * } * </wyn> * * @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" "; // 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 $status_bar"; flush(); // when done, send a newline if($done == $total) { echo" "; } } |
我不知道为什么上面的代码有许可证,我只是为了安全而复制它。以下代码没有许可证。免费用于任何目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | 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" "; } 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); } |
以下是针对UNIX机器的。
目标是检索当前的终端汇总列。(使用
这是一个基地,随时可以扩大。
1 2 3 4 5 6 7 8 9 10 | #!/usr/bin/php <?php @ob_start(); $shell = system("tput cols"); @ob_end_clean(); for( $i= 0 ; $i < $shell ; $i++ ){ echo"█"; usleep(100000); } |
假设您希望创建一个与任务进度相匹配的进度条。
只需将剩余时间(秒)除以列数。
你可能会以微秒结束,所以最好使用
这样,进度条将始终与用户外壳宽度匹配,包括调整大小时。
在纯bash中做同样的事情:
1 | for ((i=0; i<$(tput cols); i++)); do echo -e"█\c" ;done |
这显示了php
当使用循环时,比如检查间隔中的条件,另一个警告正在运行的活动的好方法是文本效果。
下面使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/usr/bin/php <?php $iloop ="0"; /* Outside the loop */ while (true){ $warn ="Program running hold on!! "; if (strlen($warn) === $iloop+1){ $iloop ="0"; } $warn = str_split($warn); $iloop++; $warn[$iloop] ="\033[35;2m\e[0m".strtoupper($warn[$iloop]); echo" \033[7m".implode($warn); usleep(90000); } |
输出:
有些人可能喜欢通过迭代ANSI代码获得的Party版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/usr/bin/php <?php $iloop ="0"; /* Outside the loop */ while (true){ for ($i=0;$i<=109;$i++){ $warn ="Program running hold on!! "; if (strlen($warn) === $iloop+1){ $iloop ="0"; } $warn = str_split($warn); $iloop++; $warn[$iloop] ="\033[$i;7m".strtoupper($warn[$iloop]); echo" \033[7m".implode($warn); usleep(90000); }} |
有关ANSI代码的更多信息,请参阅:https://stackoverflow.com/a/48365998/2494754