Updating multiple lines of perl output on terminal - perl

I am trying to update the progress of my Perl script on the terminal. The output looks something like this
Progress: ||||||||| [46%]
The progress keeps on getting updated until it reaches 100%. This is being done by printing "\r" after updating the progress. I wish to update multiple lines at the same time, how can it be done? The expectation is something like this
Progress: ||||||||| [46%]
Run-time: 100sec
After some progress(and or time) I wish to update it like this
Progress: |||||||||| [50%]
Run-time: 150sec
I tried printing "\r" two times to go to the previous line. But it didn't work.
I found similar questions (here and here), but they were answered for Python using modules. Mine is a Perl script, and I am not preferring to use external modules.

Term::ANSIScreen provides terminal control using ANSI escape sequences:
use Term::ANSIScreen qw!savepos loadpos!;
print savepos();
for my $i (1..10) {
print '|' x $i, "\n";
print "Step: $i\n";
sleep 1;
print loadpos();
}
or
use Term::ANSIScreen qw!up!;
for my $i (1..10) {
print '|' x $i, "\n";
print "Step: $i\n";
sleep 1;
print up(2);
}
These constants can be used instead of the module:
my $savepos = "\e[s";
my $loadpos = "\e[u";
my $up2 = "\e[2A";
ANSI escape codes

Related

print doesn't work while iterations are going inside foreach loop

I try to find a way to print a progressbar on the commandline while parsing logfiles. Get logfiles=> foreach file => foreach line {do}.
The idea: I want to print a part of the progressbar in every "foreach file" loop. Meaing: print the whole bar if you just parse 1 file. print half of the bar for every file when u parse 2 files and so on. You find the specific code at the bottom.
The problem: The output (print "*") is printed after ALL foreach iteration are done - not in between. Details are in the Code.
Does someone have an idea how to print inside a foreach? Or can tell me the problem? I don't get it :(.
my #logfiles=glob($logpath);
print "<------------------>\n";
$vari=20/(scalar #logfiles);
foreach my $logfile (#logfiles){
open(LOGFILEhandle, $logfile);
#lines = <LOGFILEhandle>;
print "*" x $vari; #won't work, only after loop. Even a "print "*";" doesn't work
foreach my $line (#lines){
#print "*"; works "in between". print "*" x $vari; does not.
if ($line=~/xyz/){
......
......
}
close(LOGFILEhandle);
}
}
I would like to suggest Term::ProgressBar module to avoid reinventing the wheel.
#!/usr/bin/perl
use strict;
use warnings;
use Term::ProgressBar;
my #files = qw (file1 file2 file3 file4);
my $progress = Term::ProgressBar->new(scalar #files);
for (0..#files) {
$| = 1;
sleep(1); #introducing sleep for demo purpose otherwise bar will fill up quickly
#open the file, do some operations and when you are done
#update the bar
$progress->update($_);
}
You are suffering from buffering. The output is buffered until a certain amount is reached or you print a newline. To change this behaviour simply add
$| = 1 ;
at the top of your file. This will turn on autoflush for STDOUT.
There is more than one way to do it and a little bit longer and less cryptic is Borodins suggestion:
STDOUT->autoflush();

using printf to create columnar data

I am new to perl and scripting in general. I have five variables that hold data and I need to print them as five columns next to each other. Here is the code I have now.
$i = 0;
foreach $line (<inf>){
chomp $line;
#line=split / +/, $line;
$i = $i + 1;
if ($i > $n+1) {
$i = 1;
$numdata = $numdata + 1;
}
if ($i == 1) {
printf "%20s\n", $n, "\n";
} else {
print $i-1, "BEAD", $line[$col], $line[$col+1], $line[$col+2], "\n";
}
# other statistics
}
The output I get from this looks like:
5
1BEAD0.00000e+000.00000e+000.00000e+00
2BEAD0.00000e+000.00000e+000.00000e+00
3BEAD0.00000e+000.00000e+000.00000e+00
4BEAD0.00000e+000.00000e+000.00000e+00
5BEAD0.00000e+000.00000e+000.00000e+00
5
1BEAD9.40631e-02-3.53254e-022.09369e-01
2BEAD-6.69662e-03-3.13492e-012.62915e-01
3BEAD2.98822e-024.60254e-023.61680e-01
4BEAD-1.45631e-013.45979e-021.50167e-01
5BEAD-5.57204e-02-1.51673e-012.95947e-01
5
1BEAD8.14225e-028.10216e-022.76423e-01
2BEAD2.36992e-02-2.74023e-014.47334e-01
3BEAD1.23492e-011.12571e-012.59486e-01
4BEAD-2.05375e-011.25304e-011.85252e-01
5BEAD5.54441e-02-1.30280e-015.82256e-01
I have tried using "%6d %9d %15.6f %28.6f %39.6f\n" before the variables in my print statement to try to space the data out; however, this did not give me the columns I hoped for. Any help/ suggestions are appreciated.
If you're using Perl and doing more complex stuff, you may want to look into perlform, which is designed for this kind of thing, or a module like Text::Table.
As for using printf though, you can use the padding specifiers to get consistent spacing. For instance, using the Perl docs on it, make sure the field width is before the .: your printf string should probably look something more like this (check out the "precision, or maximum width" section):
printf "%6.d %9.d %15.6f %28.6f %39.6f"
Also, if your things are in an array, you can just pass the array the second argument to printf and save yourself typing everything out. I've also prepended the two other items from your example with unshift:
unshift(#line, $i-1, "BEAD");
printf "%6.d %10s %15.6f %28.6f %39.6f\n", $line;
Note that the %s placeholders don't have the . precision specifier, so leave it out for that. If you want the e-notation for the numbers, use %e or %g instead of %f (%39.6e).
Also, for Perl questions, always check out Perl Monks - much of this answer was culled from a question there.
P.S. Given one of your example columns, here's the proof-of-concept script I tried to make sure everything worked:
perl -e '#line = (8.14225e-02,8.10216e-02,2.76423e-01);
unshift(#line, 4, "BEAD");
printf "%6.d %10s %15.6f %28.6f %39.6e\n", #line;'

How to create a waiting ellipsis in a Perl program?

The following code is not working as it seems it should be:
print "Processing your listing";
sleep($n);
print ".";
sleep($n);
print ".";
sleep($n);
print ".\n\n";
Trying to get something where there is a ., then another ., then another ., and then Process complete!
By default Perl buffers your output. To get stuff like this to work you need to disable it.
Use
$| = 1
or, rather better
STDOUT->autoflush
at the start of your program.
I'm actually going to suggest against doing this manually. There are fantastic CPAN modules which provide the feature you actually want, a progress bar. For example there are:
ProgressBar::Stack
Text::ProgressBar::Bar
Term::ProgressBar
Here's the example from ProgressBar::Stack:
use ProgressBar::Stack;
init_progress;
sleep(1);
update_progress 20;
sleep(2);
update_progress 60;
sleep(2);
update_progress 100;
print "\n";
init_progress(message => "Calculating");
my $sum = 0;
for_progress {
$sum+=$_;
sleep(1);
} 0..10;
print "\nSum = $sum\n";

Update command line output

My program (which happens to be in Perl, though I don't think this question is Perl-specific) outputs status messages at one point in the program of the form Progress: x/yy where x and yy are a number, like: Progress: 4/38.
I'd like to "overwrite" the previous output when a new status message is printed so I don't fill the screen with status messages. So far, I've tried this:
my $progressString = "Progress\t$counter / " . $total . "\n";
print $progressString;
#do lots of processing, update $counter
my $i = 0;
while ($i < length($progressString)) {
print "\b";
++$i;
}
The backspace character won't print if I include a newline in $progressString. If I leave out the newline, however, the output buffer is never flushed and nothing prints.
What's a good solution for this?
Use autoflush with STDOUT:
local $| = 1; # Or use IO::Handle; STDOUT->autoflush;
print 'Progress: ';
my $progressString;
while ...
{
# remove prev progress
print "\b" x length($progressString) if defined $progressString;
# do lots of processing, update $counter
$progressString = "$counter / $total"; # No more newline
print $progressString; # Will print, because auto-flush is on
# end of processing
}
print "\n"; # Don't forget the trailing newline
Say
$| = 1
somewhere early in your program to turn autoflushing on for the output buffer.
Also consider using "\r" to move the cursor back to the beginning of the line, rather than trying to explicitly count how many spaces you need to move back.
Like you said, don't print out a newline while your progress counter is running or else you will print out your progress on a separate line instead of overwriting the old line.
I know it's not quite what you asked for, but possibly better. I happened on this same problem and so rather than deal with it too much went to using Term::ProgressBar which looks nice too.
You can also use the ANSI escape codes to directly control the cursor. Or you can use Term::ReadKey to do the same thing.
I had to tackle something similar to this today.
If you don't mind reprinting the entire line, you could do something like this:
print "\n";
while (...) {
print "\rProgress: $counter / $total";
# do processing work here
$counter++;
}
print "\n";
The "\r" character is a carriage return-- it brings the cursor back to the beginning of the line. That way, anything you print out overwrites the previous progress notification's text.

code working fine on Perl 5.12.1 doesn't work on 5.10.0

I wrote some code (about 100 lines) that is working fine on version 5.12.1.
Unfortunately my client is using version 5.10.0. So I tested the code on 5.10.0 and found it doesn't work!
Where can I find a list of differences between 5.10 and 5.12?
Edit:
I think the best answer to the question of "Where can I find a list of differences between 5.10 and 5.12" is plusplus' comment under the "accepted answer". For the explanation of the code below, please read Michael Carman's answer.
The code that works on 5.12.1 but does not work on 5.10.0 ($contents is still an empty string after running the code)
# read in the first 10 lines.
my $contents = '';
for (my $i = 0; $i < 10 && ! eof; $i++) {
$contents .= <FILE>;
}
The improved code that works on both versions.
# read in the first 10 lines.
my $contents = '';
my $i = 0;
while (<FILE>) {
last if $i >= 10;
$contents .= $_;
$i++;
}
There's a bug in your first code sample. A bare eof reports status for the last filehandle read. On of the first pass through the loop you (presumably) haven't read anything yet; not anything from FILE anyway. It appears that the internal behavior of this invalid call changed. Under Perl 5.12.1 running perl -E "say eof" prints nothing. Under Perl 5.10.0 it prints "1".
Explicitly testing eof(FILE) should fix the problem.
Tangent: Your code isn't very idiomatic. A more perlish approach would be
my $content;
while(<$fh>) {
if ( 1 .. 10 ) { $content .= $_ }
else { last }
}
The idioms used are:
Use a lexical filehandle instead of a typeglob. ($fh instead of FILE)
Use the range operator .. to track the number of lines read. This form
implicitly tests against the input line number $..
Don't explicitly test for EOF (let the loop handle it).
Use last to break out of the loop early.
Look at the perldoc page. You''ll find the perldelta's there. Or post your code and have us look at it ;)