Overwriting a print statement with one that is shorter in length - perl

I am trying to overwrite a print statement in a foreach loop to create somewhat of a progress bar.
What I am doing:
my $arraySize = #listOfIps;
local $| = 1;
my $counter = 0;
my $progressString;
print 'Progress: ';
foreach my $ip (#listOfIps) {
$counter++;
print "\b" x length($progressString) if defined $progressString;
$progressString = "\r$counter / $arraySize - Working on $ip";
print $progressString;
#does stuff here but thats irrelevant to the problem
}
The problem I am having is that when the foreach loop gets to an IP that is shorter than the previous one it has printed eg 10.0.0.1 it still displays the extra characters left over from the previous longer print statement.
The problem:
Progress: 3 / 10 - Working on 200.144.223.211
then overwriting this print statement with the next smaller ip address in the array gives:
Progress: 4 / 10 - Working on 10.0.0.1223.211
and so on... when actually it should print just :
Progress: 4 / 10 - Working on 10.0.0.1
so that it does not have any of the characters from the previous print left over.
There must be something really obvious I am overlooking here, as I can't see any reason why this would not be working.

Use printf with a format that pads the IP string with spaces:
printf "\r$counter / $arraySize - Working on %-15s", $ip;
Adapt the number 15 to the length of your longest IP. If you have a variable, you can use it in the format string like so (thanks amon for sharing):
printf ".... Working on %-*s", $length, $ip;
Though that is quite excessive, since you can control all variable length strings with the printf:
printf "\r%-*s / %-*s - Working on %-*s", 5, $counter, 5, $arraySize, 15, $ip;
The \b escape is apparently a non-destructive backspace, which does not delete, according to this answer. Which would mean that it does just about the same as \r in your case.

Related

Perl: break down a string, with some unique constraints

I'm using Perl to feed data to an LCD display. The display is 8 characters wide. The strings of data to be displayed are always significantly longer than 8 characters. As such, I need to break the strings down into "frames" of 8 characters or less, and feed the "frames" to the display one at a time.
The display is not intelligent enough to do this on its own. The only convenience it offers is that strings of less than 8 characters are automatically centered on the display.
In the beginning, I simply sent the string 8 characters at a time - here goes 1-8, now 9-16, now 17-24, etc. But that wasn't especially nice-looking. I'd like to do something better, but I'm not sure how best to approach it.
These are the constraints I'd like to implement:
Fit as many words into a "frame" as possible
No starting/trailing space(s) in a "frame"
Symbol (ie. hyphen, ampersand, etc) with a space on both sides qualifies as a word
If a word is longer than 8 characters, simulate per-character scrolling
Break words longer than 8 characters at a slash or hyphen
Some hypothetical input strings, and desired output for each...
Electric Light Orchestra - Sweet Talkin' Woman
Electric
Light
Orchestr
rchestra
- Sweet
Talkin'
Woman
Quarterflash - Harden My Heart
Quarterf
uarterfl
arterfla
rterflas
terflash
- Harden
My Heart
Steve Miller Band - Fly Like An Eagle
Steve
Miller
Band -
Fly Like
An Eagle
Hall & Oates - Did It In A Minute
Hall &
Oates -
Did It
In A
Minute
Bachman-Turner Overdrive - You Ain't Seen Nothing Yet
Bachman-
Turner
Overdriv
verdrive
- You
Ain't
Seen
Nothing
Yet
Being a relative Perl newbie, I'm trying to picture how would be best to handle this. Certainly I could split the string into an array of individual words. From there, perhaps I could loop through the array, counting the letters in each subsequent word to build the 8-character "frames". Upon encountering a word longer than 8 characters, I could then repetitively call substr on that word (with offset +1 each time), creating the illusion of scrolling.
Is this a reasonable way to accomplish my goal? Or am I reinventing the wheel here? How would you do it?
The base question is to find all consecutive overlapping N-long substrings in a compact way.
Here it is in one pass with a regex, and see the end for doing it using substr.
my $str = join '', "a".."k"; # 'Quarterflash';
my #eights = $str =~ /(?=(.{8}))/g;
This uses a lookahead which also captures, and in this way the regex crawls up the string character by character, capturing the "next" eight each time.
Once we are at it, here is also a basic solution for the problem. Add words to a buffer until it would exceed 8 characters, at which point it is added to an array of display-ready strings and cleared.
use warnings;
use strict;
use feature 'say';
my $str = shift // "Quarterflash - Harden My Heart";
my #words = split ' ', $str;
my #to_display;
my $buf = '';
foreach my $w (#words) {
if (length $w > 8) {
# Now have to process the buffer first then deal with this long word
push #to_display, $buf;
$buf = '';
push #to_display, $w =~ /(?=(.{8}))/g;
}
elsif ( length($buf) + 1 + length($w) > 8 ) {
push #to_display, $buf;
$buf = $w;
}
elsif (length $buf != 0) { $buf .= ' ' . $w }
else { $buf = $w }
}
push #to_display, $buf if $buf;
say for #to_display;
This is clearly missing some special/edge cases, in particular those involving non-word characters and hyphenated words, but that shouldn't be too difficult to add.†
Here is a way to get all consecutive 8-long substrings using substr
my #to_display = map { substr $str, $_, 8 } 0..length($str)-8;
† Example, break a word with hyphen/slash when it has no spaces around it (per question)
my #parts = split m{\s+|(?<=\S)[-/](?=\S)}, $w;
The hyphen/slash is discarded as this stands; that can be changed by capturing the pattern as well and then filtering out elements with only spaces
my #parts = grep { /\S/ } split m{( \s+ | (?<=\S) [-/] (?=\S) )}x, $w;
These haven't been tested beyond just barely. Can fit in the if (length $w > 8) branch.
The initial take-- The regex was originally written with a two-part pattern. Keeping it here for record and as an example of use of pair-handling functions from List::Util
The regex below matches and captures a character, followed by a lookahead for the next seven, which it also captures. This way the engine captures 1 and 7-long substrings as it moves along char by char. Then the consecutive pairs from the returned list are joined
my $str = join '', "a".."k"; # 'Quarterflash';
use List::Util qw(pairmap);
my #eights = pairmap { $a . $b } $str =~ /(. (?=(.{7})) )/gx;
# or
# use List::Util qw(pairs);
# my #eights = map { join '', #$_ } pairs $str =~ /(.(?=(.{7})))/g;

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;'

Progress line in perl

I would like to create a very simple progressbar for my script. So far I've got this, and it works. However, I cannot get it to be a percentage out of 100. My code is the following and it produces basically a dot for every 5 entries in #entries.
my $total_entries = #entries;
my $count = 0;
my $count_tens = $total_entries/0.2;
$count_tens = sprintf ('%d',$count_tens);
foreach (#entries){
# do some stuff #
for (1 .. $total_entries){
if ($count == $count_tens){
print ".";
$count = 0;
}
$count++;
}
}
I would like to have something that produces always a fixed amount of dots, regardless of the total number of entries in #entries.
Let's say we want 80 dots. Then:
my $number_of_dots = 80;
my #items = 0 .. 20; # or something
my $items_per_dot = #items / $number_of_dots;
STDOUT->autoflush(1); # print everything out immediately
for my $i (0 .. $#items) {
my $dots = $i / $items_per_dot;
print "\r", "." x $dots;
sleep 1; # do something
}
print "\n";
Note that we avoid rounding errors by calculating the number of dots per item anew on each iteration. The \r will move the cursor to the start of the line, so the existing dots will be overwritten each time. You can easily skip the printing if the $dots value doesn't change between iterations.
Rather than rewriting the wheel, you may want to use existing code that has already been written, tested and debugged.
http://metacpan.org/pod/Term::ProgressBar

Concatenating strings from a multidimensional array overwrites the target string in Perl

I've built a two dimension array with string values. There are always 12 columns but the number of rows vary. Now I'd like to build a string of each row but when I run the following code:
$outstring = "";
for ($i=0; $i < $ctrLASTROW + 1; $i++) {
for ($k=0; $k < 12; $k++){
$datastring = $DATATABLE[$i][$k]);
$outstring .= $datastring;
}
}
$outstring takes the first value. Then on the second inner loop and subsequent loops the value in $outstring gets overlaid. For example the first value is "DATE" then the next time when the value "ABC" gets fed to it. Rather than being the hoped for "DATEABC" it's "ABCE". The "E" is the fourth character of DATE. I figure I'm missing the scalar / list issue but I've tried who knows how many variations to no avail. When I first started I tried the concatenation directly from the #DATATABLE. Same problem. Only quicker.
When you have a problem such as two strings DATE and ABC being concatenated, and the end result is ABCE, or one of the strings overwriting the other, a likely scenario is that you have a file from another OS, with the line endings \r\n, which are chomped, resulting in the string DATE\rABC when concatenated, which then becomes ABCE when printed.
In other words:
my $foo = "DATE\r\n";
my $bar = "ABC\r\n"; # \r\n line endings from file
chomp($foo, $bar); # removes \n but leaves \r
print $foo . $bar; # prints ABCE
To confirm, use
use Data::Dumper;
$Data::Dumper::Useqq = 1;
print Dumper $DATATABLE[$i][$k]; # prints $VAR1 = "DATE\rABC\r";
To resolve, instead of chomp use a regex such as:
$foo =~ s/[\r\n]+\z//;

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.