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$..$/