http://www.perlmonks.org?node_id=864405


in reply to Real Time Progress Bar in console

Corion hit the nail on the head.

The brief answer is -- make your standard output (STDOUT) flush immediately. To do so, add this line near the beginning of your script (before doing any print statments):

$| = 1;

The corresponding section from the article Corion linked to is the one entitled "Disabling Inappropriate Buffering", where it talks about making the "filehandle hot".

If you have use for a really fancy ascii-oriented progress bar, here is something I wrote that I've found useful in a lot of different applications:

# # progress_bar # # Input: $1 - A message to display with the progress bar. It can co +ntain # any of the following special 3-character strings, each + of which # will be converted to the following value: # # <b> - the progress bar itself # <c> - the current count # <p> - the percentage finished # <r> - estimated remaining time # <t> - the total count # # $2 - A total count # $3 - (optional) The 'done' symbol (default is '@') # $4 - (optional) The 'todo' symbol (default is '-') # # Output: A closure that takes a count, and updates the progress bar + based # on its value. When the progress bar is done, it should be + called # with no arguments, to show that the progress is complete - +and- to # print a final newline. ## sub progress_bar { my ($msg, $total, $done_sym, $todo_sym) = @_; ($total || 0) or fatal("total can NOT be zero/undefined!"); $done_sym ||= '@'; $todo_sym ||= '-'; $| = 1; my $start = time(); my $last = $start; my $remain = "???"; my $b_left = ($msg =~ /<r>/)? 1: 0; my $c_prog = sub { my ($count) = @_; my $b_done = defined($count)? 0: 1; $b_done and $count = $total; ($count > $total) and $count = $total; my $pcnt = sprintf "%6.2f", 100.0 * $count / $total; my $new_msg = $msg; # Calculate estimated remaining time my $time = time(); my $dtime = $time - $start; if ($b_done) { $remain = "Finished"; } elsif ($b_left and $count and $dtime and $time - $last > 3) +{ $last = $time; my $rate = ($count / $dtime); my $nsec = int(($total - $count) / $rate); my $hr = my $min = 0; if ($nsec > 3600) { $hr = int($nsec / 3600); $nsec -= 360 +0 * $hr } if ($nsec > 60) { $min = int($nsec / 60); $nsec -= 60 +* $min } if ($hr) { $remain = sprintf "%02d:%02d:%02d", $hr, $min, $nsec; } elsif ($min) { $remain = sprintf "%02d:%02d", $min, $nsec; } else { my $s = (1 == $nsec)? "": "s"; $remain = "$nsec second$s"; } } $new_msg =~ s/<c>/$count/g; $new_msg =~ s/<p>/$pcnt/g; $new_msg =~ s/<t>/$total/g; $new_msg =~ s/<r>/$remain/g; my $len = 79 + 3 - length($new_msg); my $ndone = int($len * $count / $total); my $ntodo = $len - $ndone; my $bar = ($done_sym x $ndone) . ($todo_sym x $ntodo); $new_msg =~ s/<b>/$bar/; print "$new_msg\r"; $b_done and print "\n"; }; return $c_prog; }

You can call it like this ... let's say you were processing a bunch of files, and wanted progress indication for each file processed:

use strict; use warnings; my @files = ( 'some', 'list', 'of', 'files' ); my $nfiles = @files; my $nhandled = 0; my $c_prog = progress_bar("File #<c> of <t> [<p>%] <b>", $nfiles); foreach my $file (@files) { do_something_with_this_file($file); $c_prog->(++$nhandled); } $c_prog->();

Note that you don't want to print anything while the progress bar is displaying, or it will disrupt the progress bar's output. :)

Subroutine do_something_with_this_file left as an exercise to the reader.


s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/