PHP 中的命令行进度条
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2124195/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Command line progress bar in PHP
提问by Andrei Serdeliuc ?
I am currently trying to add a progress bar to a command line script and I've tried various solutions (including Zend and Console_ProgressBar). The problem they both have in common is that the progress bar doesn't stick at the bottom of the window because during the script, new lines and other information is outputted.
我目前正在尝试向命令行脚本添加进度条,并且尝试了各种解决方案(包括 Zend 和 Console_ProgressBar)。它们的共同点是进度条不会停留在窗口底部,因为在执行脚本时,会输出新行和其他信息。
Is there any way to keep the progress bar at the bottom of the terminal but still be able to output other information while the script is running?
有什么办法可以将进度条保留在终端底部,但在脚本运行时仍然能够输出其他信息?
[Edit]
[编辑]
I figured it out:
我想到了:
Instead of outputting directly to STDOUT I am actually grabbing the output inside a variable, I erase the screen with echo chr(27) . '[2J'and then output to STDOUT the contents of the variable and then append my progress bar.
我实际上不是直接输出到 STDOUT,而是在变量中获取输出,而是擦除屏幕,echo chr(27) . '[2J'然后将变量的内容输出到 STDOUT,然后附加我的进度条。
Hope that makes sense :)
希望这是有道理的:)
回答by user956584
This is nice progress bar for cli:
这是 cli 的不错进度条:
http://snipplr.com/view/29548/
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";
}
}
?>
回答by MacroMan
Other answers seem overly complex. My solution is to simply echo \033[0G escape sequence before the next update and it moves the cursor back to the beginning.
其他答案似乎过于复杂。我的解决方案是在下一次更新之前简单地 echo \033[0G 转义序列并将光标移回开头。
function progressBar($done, $total) {
$perc = floor(($done / $total) * 100);
$left = 100 - $perc;
$write = sprintf("3[0G3[2K[%'={$perc}s>%-{$left}s] - $perc%% - $done/$total", "", "");
fwrite(STDERR, $write);
}
Calling the function for the first time outputs the progress bar and each subsequent call overwrites the last line with a new progress bar.
第一次调用该函数会输出进度条,随后的每次调用都会用新的进度条覆盖最后一行。
EDIT: I have changed echoing \r to the escape sequence \033[0G and this should now work on OSX as well as Linux/Unix.
编辑:我已将回显 \r 更改为转义序列 \033[0G,这现在应该适用于 OSX 以及 Linux/Unix。
EDIT 2: Fixed possible error on line 3 as per @tbjers suggestion.
编辑 2:根据@tbjers 的建议修复了第 3 行可能出现的错误。
EDIT 3: Updated with new version that prints to STDERR and now also on GitHub: https://github.com/MacroMan/PHPTerminalProgressBar
编辑 3:更新为打印到 STDERR 的新版本,现在也在 GitHub 上:https: //github.com/MacroMan/PHPTerminalProgressBar
回答by over_optimistic
This is an improvement of the previous answer which handles terminal resizes and uses 2 lines instead of 1. First line is info like time/percent second line is the progress bar.
这是对处理终端调整大小并使用 2 行而不是 1 行的先前答案的改进。第一行是诸如时间/百分比之类的信息,第二行是进度条。
<?
/*
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";
}
}
I don't know why the above code has a license I'm just copying it to be safe. The bellow code is under no license. Free to use for any purpose.
我不知道为什么上面的代码有许可证,我只是为了安全起见复制它。以下代码未经许可。免费用于任何目的。
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);
}
回答by NVRM
The following is for unix machines.
以下是针对unix 机器的。
The goal is to retrieve the current terminal total columns. (using tput)
目标是检索当前终端总列。(使用tput)
This is a base, ready to be expanded.
这是一个基础,准备扩展。
#!/usr/bin/php
<?php
@ob_start();
$shell = system("tput cols");
@ob_end_clean();
for( $i= 0 ; $i < $shell ; $i++ ){ echo "█"; usleep(100000); }
The obactions are here to hide the stdoutof tput.
该ob行动在这里隐藏stdout的tput。
Let's say you would like to make a progress bar matching your task progress.
假设您想制作一个与您的任务进度相匹配的进度条。
Just dividethe remaining time in seconds by the numbers of columns.
只需将剩余时间(以秒为单位)除以列数。
You may end up with microseconds, so it's best to use usleep.
您可能会以微秒结束,因此最好使用usleep.
This way the progress bar will always match the user shell width, including when resized.
这样,进度条将始终与用户外壳宽度匹配,包括调整大小时。
To do the same thing in pure bash:
在纯 bash 中做同样的事情:
for ((i=0; i<$(tput cols); i++)); do echo -e "█\c" ;done
This shows the main singularity of the php echo: it does not append a new line, while the bash echodoes.
这显示了 php 的主要奇点echo:它不添加新行,而 bashecho会。
When using loops, say to check for a condition in intervals, one other nice way to warn for running activity is text effects.
使用循环时,比如说要检查某个时间间隔的条件,警告正在运行的活动的另一种好方法是text effects。
The following using strtoupperand the ansi code reverse video.
以下使用strtoupper和ansi代码反向视频。
#!/usr/bin/php
<?php
$iloop = "0"; /* Outside the loop */
while (true){
$warn = "Program running hold on!!\r";
if (strlen($warn) === $iloop+1){
$iloop = "0";
}
$warn = str_split($warn);
$iloop++;
$warn[$iloop] = "3[35;2m\e[0m".strtoupper($warn[$iloop]);
echo " 3[7m".implode($warn);
usleep(90000);
}
Output:
输出:
Some may likes the party version, obtained by iteration of ansi codes.
有些人可能喜欢通过 ansi 代码迭代获得的派对版本。
#!/usr/bin/php
<?php
$iloop = "0"; /* Outside the loop */
while (true){
for ($i=0;$i<=109;$i++){
$warn = "Program running hold on!!\r";
if (strlen($warn) === $iloop+1){
$iloop = "0";
}
$warn = str_split($warn);
$iloop++;
$warn[$iloop] = "3[$i;7m".strtoupper($warn[$iloop]);
echo " 3[7m".implode($warn);
usleep(90000);
}}
See more about ANSIcodes: https://stackoverflow.com/a/48365998/2494754
查看更多关于ANSI代码:https: //stackoverflow.com/a/48365998/2494754


