48

I'm trying to get a "live" progress indicator working on my php CLI app. Rather than outputting as

1Done
2Done
3Done

I would rather it cleared and just showed the latest result. system("command \C CLS") doesnt work. Nor does ob_flush(), flush() or anything else that I've found.

I'm running windows 7 64 bit ultimate, I noticed the command line outputs in real time, which was unexpected. Everyone warned me that out wouldn't... but it does... a 64 bit perk?

Cheers for the help!

I want to avoid echoing 24 new lines if I can.

hakre
  • 193,403
  • 52
  • 435
  • 836
  • Well traditionally clear screen is dealt with by the Form Feed character (FF - ASCII-12). – Orbling Dec 01 '10 at 00:27
  • Can you provide ASCII code to php. I tried $str = sprintf("Delete a let%cter, 127); but it didnt work as expected. -php.net/chr() –  Dec 01 '10 at 01:05
  • You could also possibly use `Backspace` ASCII-8. – Kendall Hopkins Dec 01 '10 at 01:09
  • I tried delete 0x7F however I get a small triangle instead of a deletion. I could use backspace, but the cursor is at the start of the line. –  Dec 01 '10 at 01:21
  • I wrote a comprehensive answer here, works like a charm, tested on Windows linux and OSX, a one liner: https://stackoverflow.com/a/40572401/6334301 – Dom Jan 27 '23 at 21:12

12 Answers12

118

Try outputting a line of text and terminating it with "\r" instead of "\n".

The "\n" character is a line-feed which goes to the next line, but "\r" is just a return that sends the cursor back to position 0 on the same line.

So you can:

echo "1Done\r";
echo "2Done\r";
echo "3Done\r";

etc.

Make sure to output some spaces before the "\r" to clear the previous contents of the line.

[Edit] Optional: Interested in some history & background? Wikipedia has good articles on "\n" (line feed) and "\r" (carriage return)

dkamins
  • 21,450
  • 7
  • 55
  • 59
  • 4
    This looks like it could work well. For anyone else I plan on capturing the length of each output to ensure that enough output is provided to erase the previous output. –  Dec 01 '10 at 00:54
  • 1
    I'd suggest putting the \r at the front and end of the buffer, that way if the user inputs something (between the buffers), it doesn't get prepended to the next buffer. – Kendall Hopkins Dec 01 '10 at 01:05
  • 2
    +1 One of the rare advantages of the double-character newline. Seems there is a use in the modern age for the carriage return (CR) as well as the line feed (LR). Once this was exploited heavily in terminal-based software, old methods. I guess people do not learn their control characters any more, one of the first things I really had to learn! – Orbling Dec 01 '10 at 01:06
  • 2
    This wont work across multiple lines unfortunately as the \r will only return the cursor to the start of the current line. –  Dec 01 '10 at 13:30
  • 1
    Awesome. Exactly what I was looking for! :-) – Dr.Kameleon May 15 '13 at 13:38
  • Heads up: This is the answer to the question "How can I return the cursor to the beginning of it's line?" But is neither the answer to the question "How do I clear a line?" or "How do I replace my output?", least of all "How do I clear the screen?" (as the cmd `system("command \C CLS")` indicates was kind of the question). – Mike Jan 08 '15 at 22:48
  • `echo str_pad("1Done", 120)."\r";` -- It "clears" the line: it prints the text, then prints whitespaces until 120 chars, then the cursor returns. – borazslo Jan 06 '16 at 05:33
  • +1 because it works and I now finally get the real difference between new line and carriage return :) Thanks for that! – Manuel Mannhardt Sep 12 '17 at 13:51
36

I came across this while searching for a multi line solution to this problem. This is what I eventually came up with. You can use Ansi Escape commands. http://www.inwap.com/pdp10/ansicode.txt

<?php
function replaceOut($str)
{
    $numNewLines = substr_count($str, "\n");
    echo chr(27) . "[0G"; // Set cursor to first column
    echo $str;
    echo chr(27) . "[" . $numNewLines ."A"; // Set cursor up x lines
}

while (true) {
    replaceOut("First Ln\nTime: " . time() . "\nThird Ln");
    sleep(1);
}
?>
nolanpro
  • 2,257
  • 3
  • 23
  • 23
13

I recently wrote a function that will also keep track of the number of lines it last output, so you can feed it arbitrary string lengths, with newlines, and it will replace the last output with the current one.

With an array of strings:

$lines = array(
    'This is a pretty short line',
    'This line is slightly longer because it has more characters (i suck at lorem)',
    'This line is really long, but I an not going to type, I am just going to hit the keyboard... LJK gkjg gyu g uyguyg G jk GJHG jh gljg ljgLJg lgJLG ljgjlgLK Gljgljgljg lgLKJgkglkg lHGL KgglhG jh',
    "This line has newline characters\nAnd because of that\nWill span multiple lines without being too long",
    "one\nmore\nwith\nnewlines",
    'This line is really long, but I an not going to type, I am just going to hit the keyboard... LJK gkjg gyu g uyguyg G jk GJHG jh gljg ljgLJg lgJLG ljgjlgLK Gljgljgljg lgLKJgkglkg lHGL KgglhG jh',
    "This line has newline characters\nAnd because of that\nWill span multiple lines without being too long",
    'This is a pretty short line',
);

One can use the following function:

function replaceable_echo($message, $force_clear_lines = NULL) {
    static $last_lines = 0;

    if(!is_null($force_clear_lines)) {
        $last_lines = $force_clear_lines;
    }

    $term_width = exec('tput cols', $toss, $status);
    if($status) {
        $term_width = 64; // Arbitrary fall-back term width.
    }

    $line_count = 0;
    foreach(explode("\n", $message) as $line) {
        $line_count += count(str_split($line, $term_width));
    }

    // Erasure MAGIC: Clear as many lines as the last output had.
    for($i = 0; $i < $last_lines; $i++) {
        // Return to the beginning of the line
        echo "\r";
        // Erase to the end of the line
        echo "\033[K";
        // Move cursor Up a line
        echo "\033[1A";
        // Return to the beginning of the line
        echo "\r";
        // Erase to the end of the line
        echo "\033[K";
        // Return to the beginning of the line
        echo "\r";
        // Can be consolodated into
        // echo "\r\033[K\033[1A\r\033[K\r";
    }

    $last_lines = $line_count;

    echo $message."\n";
}

In a loop:

foreach($lines as $line) {
    replaceable_echo($line);
    sleep(1);
}

And all lines replace each other.

The name of the function could use some work, just whipped it up, but the idea is sound. Feed it an (int) as the second param and it will replace that many lines above instead. This would be useful if you were printing after other output, and you didn't want to replace the wrong number of lines (or any, give it 0).

Dunno, seemed like a good solution to me.

I make sure to echo the ending newline so that it allows the user to still use echo/print_r without killing the line (use the override to not delete such outputs), and the command prompt will come back in the correct place.

Mike
  • 1,968
  • 18
  • 35
12

i know the question isn't strictly about how to clear a SINGLE LINE in PHP, but this is the top google result for "clear line cli php", so here is how to clear a single line:

function clearLine()
{
    echo "\033[2K\r";
}
hanshenrik
  • 19,904
  • 4
  • 43
  • 89
9
function clearTerminal () {
  DIRECTORY_SEPARATOR === '\\' ? popen('cls', 'w') : exec('clear');
}

Tested on Win 7 PHP 7. Solution for Linux should work, according to other users reports.

Alexander Shostak
  • 673
  • 10
  • 11
  • 4
    FINALLY! SOMETHING THAT ACTUALLY WORKS! – Steven Martin Dec 31 '17 at 22:02
  • 4
    Yeah seriously, it's insane how many non-working "solutions" I had to try before finding yours. THANK YOU! – daninthemix Jul 24 '18 at 08:21
  • Welcome ) Replacing single line can be achieved via "\x07\x20" codes (backspace + space), but it seems that only popen allows 'cls' child process to inherit console descriptors and clear the whole window using native API. – Alexander Shostak Jul 25 '18 at 17:16
  • 1
    this leaks resources (PID and memory leak), which makes it very unsuitable for long-running scripts, this should be `$p=popen('cls', 'w');pclose($p);` – hanshenrik Jan 18 '19 at 18:43
  • hanshenrik, are you sure, that php does not automatically destroy the result of popen? Reference count reaches zero, resource destructor must be called automatically. – Alexander Shostak Jan 19 '19 at 20:42
  • @hanshenrik nice idea.. Why not instead `pclose(popen('cls', 'w'));` ?? – ezio4df Feb 21 '20 at 13:33
  • 1
    @aXuser264, pclose will be called automatically for resource without references. But it's ok to call it manually. – Alexander Shostak Feb 21 '20 at 16:23
6

something like this :

for ($i = 0; $i <= 100; $i++) {
    echo "Loading... {$i}%\r";
    usleep(10000);
}
Bruno Ribeiro
  • 1,280
  • 16
  • 21
3

Use this command for clear cli:

echo chr(27).chr(91).'H'.chr(27).chr(91).'J';   //^[H^[J  
Nabi K.A.Z.
  • 9,887
  • 6
  • 59
  • 81
2
function (int $count = 1) {
    foreach (range(1,$count) as $value){
        echo "\r\x1b[K"; // remove this line
        echo "\033[1A\033[K"; // cursor back
    }
}

See the full example here

BlackHammer
  • 91
  • 1
  • 3
1

Console functions are platform dependent and as such PHP has no built-in functions to deal with this. system and other similar functions won't work in this case because PHP captures the output of these programs and prints/returns them. What PHP prints goes to standard output and not directly to the console, so "printing" the output of cls won't work.

casablanca
  • 69,683
  • 7
  • 133
  • 150
1
<?php
error_reporting(E_ERROR | E_WARNING | E_PARSE);

function bufferout($newline, $buffer=null){
    $count = strlen(rtrim($buffer));
    $buffer = $newline;
    if(($whilespace = $count-strlen($buffer))>=1){
        $buffer .= str_repeat(" ", $whilespace);
    }
    return $buffer."\r"; 
};

$start = "abcdefghijklmnopqrstuvwxyz0123456789";
$i = strlen($start);

while ($i >= 0){
    $new = substr($start, 0, $i);
    if($old){
        echo $old = bufferout($new, $old);
    }else{
        echo $old = bufferout($new);
    }
    sleep(1);
    $i--;
}
?>

A simple implementation of @dkamins answer. It works well. It's a bit- hack-ish. But does the job. Wont work across multiple lines.

Mike
  • 1,968
  • 18
  • 35
0

Unfortunately, PHP 8.0.2 does not has a function to do it. However, if you just want to clear console try this: print("\033[2J\033[;H"); or use : proc_open('cls', 'w');

It works in php 8.0.2 and windows 10. It is the same that system('cls') using c language programing.

cwallenpoole
  • 79,954
  • 26
  • 128
  • 166
-1

Tried some of solutions from answers:

<?php
...
    $messages = [
        '11111',
        '2222',
        '333',
        '44',
        '5',
    ];

    $endlines = [
        "\r",
        "\033[2K\r",
        "\r\033[K\033[1A\r\033[K\r",
        chr(27).chr(91).'H'.chr(27).chr(91).'J',
    ];
    foreach ($endlines as $i=>$end) {
        foreach ($messages as $msg) {
            output()->write("$i. ");
            output()->write($msg);
            sleep(1);
            output()->write($end);
        }
    }

And \033[2K\r seems like works correct.

igormukhingmailcom
  • 465
  • 1
  • 6
  • 9