Enhancing Terminal Output in Perl
July 01, 2003
URL:http://www.drdobbs.com/web-development/enhancing-terminal-output-in-perl/184416028
The Perl Journal July 2003
Shay has worked with transaction processing systems at CCBill LLC, for the last five years and can be contacted at [email protected].
This article discusses how to make terminal output easier to read and monitor. If you are like me and spend most of the day at a UNIX command prompt, you'll probably benefit from using Term::Report and Term::StatusBar, two modules I created for making terminal output easier to track. They can be used separately, but are best if used together. These modules have few dependencies, which are loaded as required, so there is no need to install anything new.
Typically, programs send their output to STDOUT or STDERR. If there are too many lines sent to the terminal, they end up scrolling and may be irretrievable depending on your terminal's buffer size. You can send the output to a file, but then how do you monitor the program to make sure everything is going well? You could tail -f <file>, but then you run into the same problem of output scrolling off of the screen.
The most important components of useful terminal output are:
1. The ability to see that the program is processing.
2. The ability to see what the program is doing.
3. The ability to track the program's progress.
With these criteria met, there is no doubt about whether the program has hung or what progress it has made in processing the data.
A simple example of terminal output seen in many scripts is shown in Listing 1. This first example will not utilize Term::Report or Term::StatusBar. This will be modified later in order to show how the Term modules work and how they can be useful.
This listing prints the output to the terminal. If there were thousands of items to iterate over, the output would end up scrolling off the screen. Another problem is that there is no way to gauge progress and tell how long it might take to finish. The data sent to the terminal ends up being cluttered and not very useful. In processing larger data sets, it would be difficult to determine the program's output by using this method.
The previous output can be organized with the help of Term::Report. With minor alterations, the amount of output can be reduced, thus improving readability; see Listing 2.
The use of Term::Report usually doesn't get any more complicated than the aforementioned example. But even this simple implementation of Term::Report makes the output more organized and easier to follow. It is readily apparent that the program is working and what data it is processing. Two important criteria for useful terminal output have been met.
If there are thousands of items to iterate over, the constructor can be changed as shown below:
my $report = Term::Report->new(startRow => 1, numFormat => 1);This would format numbers using Number::Format (i.e., 1000 becomes 1,000). This makes it even easier to read the output quickly. The last criterion, the ability to track the program's progress, is accomplished by using Term::StatusBar. Rather than create a separate object, use Term::Report's ability to wrap the Term::StatusBar module. (See Listing 3.) Notice that Term::Report has been used to create a status bar. The status bar needs to know how many items there are to process. Then it's just a matter of calling StatusBar->update() with each iteration of data processing. When updating the inventory in the aforementioned example, the status bar is reset rather than creating a new object. To tell the status bar to empty rather than fill, pass reverse => 1 to the reset() method. This is a recent addition to Term::StatusBar. Calling the printBarReport() method outputs our statistics summary. This prints a horizontal bar chart based on the final values and scale of the status bar. With these minor changes, all three criteria for useful terminal output are satisfied. Use the subText and subTextAlign methods of Term::StatusBar to enhance the output further. These place information just under the status bar to show what the program is currently processing.
my $report = Term::Report->new( startRow => 4, numFormat => 1, statusBar => [ label => 'Widget Analysis: ', subText => 'Locating widgets', subTextAlign => 'center' ], ); ... if (!($_%int((rand(10)+rand(10)+1)))){ $report->finePrint('discarded', 0, ++$discard); $status->subText("Discarding bad widget"); } else{ $status->subText("Locating widgets"); }Another new addition to Term::StatusBar is the showTime parameter. When turned on, an estimated time to completion is placed at the top of the status bar. Notice in the code below, the value of startRow has changed. This is to allow space for the estimated completion time. In a future version, this sort of manual adjustment probably will not be necessary.
my $report = Term::Report->new( startRow => 5, numFormat => 1, statusBar => [ label => 'Widget Analysis: ', subText => 'Locating widgets', subTextAlign => 'center', showTime => 1 ],);If the module is unable to figure out the estimated time, then "00:00:00" will be displayed. When using the reverse method, there is no estimated time tracked. This will be possible in future releases of the module.
#!/usr/bin/perl $|++; use Time::HiRes qw(usleep); my ($items, $discard) = (100,0); ## Monitor inventory (L=Locating; D=Discarding) for (1..$items){ if (!($_%int((rand(10)+rand(10)+1)))){ $discard++; print " D "; } else { print "L"; } usleep(50000); } print "\n"; ## Update inventory for (1..($items-$discard)){ print "U"; } print "\n\n\n Summary for widgets: \n\n". " Total: $items\n". " Good Widgets: ".($items-$discard)."\n". " Bad Widgets: $discard\n\n";Back to Article
#/usr/bin/perl $|++; use Time::HiRes qw(usleep); use Term::Report; my $report = Term::Report->new(startRow => 1); my ($items, $discard) = (100,0); $report->savePoint('total', "Total widgets: ", 1); $report->savePoint('discarded', "\n Widgets discarded: ", 1); ## Monitor inventory for (1..$items){ $report->finePrint('total', 0, $_); if (!($_%int((rand(10)+rand(10)+1)))){ $report->finePrint('discarded', 0, ++$discard); } usleep(50000); } ## Update inventory $report->savePoint('inventory', "\n\nInventorying widgets... ", 1); for (1..($items-$discard)){ $report->finePrint('inventory', 0, $_); } $report->printLine("\n\n\n\n Summary for widgets: \n\n"); $report->printLine(" Total: $items\n"); $report->printLine(" Good Widgets: ".($items-$discard)."\n"); $report->printLine(" Bad Widgets: $discard\n");Back to Article
#!/usr/bin/perl $|++; use Time::HiRes qw(usleep); use Term::Report; my $report = Term::Report->new( startRow => 4, numFormat => 1, statusBar => [ label => 'Widget Analysis: ', ], ); my ($items, $discard) = (100,0); my $status = $report->{statusBar}; $status->setItems($items); $status->start; $report->savePoint('total', "Total widgets: ", 1); $report->savePoint('discarded', "\n Widgets discarded: ", 1); ## Monitor inventory for (1..$items){ $report->finePrint('total', 0, $_); if (!($_%int((rand(10)+rand(10)+1)))){ $report->finePrint('discarded', 0, ++$discard); } usleep(50000); $status->update; } ## Update inventory $status->reset({ reverse=>1, setItems=>($items-$discard), start=>1 }); $report->savePoint( 'inventory', "\n\nInventorying widgets... ", 1 ); for (1..($items-$discard)){ $report->finePrint('inventory', 0, $_); $status->update; } $report->printBarReport( "\n\n\n\n Summary for widgets: \n\n", { " Total: " => $items, " Good Widgets: " => $items-$discard, " Bad Widgets: " => $discard, } );
Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.