Formatting a hash content to generate a report

Viewed 120

I have a hash which contains data like below:

my %hash = (
    '150' => {
             'priority' => 'High',
             'node' => 'Node1',
             'delta' => '00:05:00'
           },
    '170' => {
             'delta' => '00:00:30',
             'node' => 'Node2',
             'priority' => 'Medium'
           }
);

I am iterationg it using foreach loop and generating a report (.txt) file.

So, format I need is something like below:

EVENTID              NODE                 DELTA                PRIORITY
-------------------- -------------------- -------------------- --------------------
150                  Node1                00:05:00             High
170                  Node2                00:00:30             Medium

Below is my script which formats the result:

...
...
open(my $fh, '>', "report_file.txt") or die "Cannot open a file : $!";

printf $fh("%-20s %-20s %-20s %-20s\n", 'EVENTID', 'NODE', 'DELTA', 'PRIORITY');
printf $fh("%-20s %-20s %-20s %-20s\n", '-'x20, '-'x20, '-'x20, '-'x20);

foreach my $key (sort keys %hash){
    printf $fh("%-20s %-20s %-20s %-20s\n", $key, $hash{$key}{'node'},$hash{$key}{'delta'},$hash{$key}{'priority'});
}
close $fh;

print "END\n";

Its giving me the report as expected, but want to confirm whether its a right approch? Because I am hardcoding spaces (-20s) here. Also dashes(-) which will act as underline for the header is also mentioned like '-'x20.

Is it a good approch or do we have any alternate way to acomplish this (any predefined Perl module) ?

3 Answers

Perl provides format for data output and it best fit for your goal

use strict;
use warnings;

my %events = (
    '150' => {
             'priority' => 'High',
             'node' => 'Node1',
             'delta' => '00:05:00'
           },
    '170' => {
             'delta' => '00:00:30',
             'node' => 'Node2',
             'priority' => 'Medium'
           }
);

$^ = "STDOUT_TOP";

my($event,$priority,$node,$delta);

for $event (sort keys %events) {
    ($node,$delta,$priority) = @{$events{$event}}{qw/node delta priority/};
    write;
}

format STDOUT_TOP = 
EVENTID              NODE                 DELTA                PRIORITY
-------------------- -------------------- -------------------- --------------------
.

format STDOUT =
@<<<<                @<<<<<<<<<<<         @<<<<<<<<<<<         @<<<<<<<<<<<
$event,$node,$delta,$priority
.

Output

EVENTID              NODE                 DELTA                PRIORITY
-------------------- -------------------- -------------------- --------------------
150                  Node1                00:05:00             High
170                  Node2                00:00:30             Medium

ADDENDUM: Following code demonstrates how to use formats to write output into a file

use strict;
use warnings;

my %events = (
    '150' => {
             'priority' => 'High',
             'node' => 'Node1',
             'delta' => '00:05:00'
           },
    '170' => {
             'delta' => '00:00:30',
             'node' => 'Node2',
             'priority' => 'Medium'
           }
);

my $fname = 'hash_events.txt';

open FILE, '>', $fname
    or die "Couldn't open $fname: $!";
    
select(FILE);

$^ = "FILE_TOP";

my($event,$priority,$node,$delta);

for $event (keys %events) {
    ($node,$delta,$priority) = @{$events{$event}}{qw/node delta priority/};
    write;
}

close FILE;

format FILE_TOP = 
EVENTID              NODE                 DELTA                PRIORITY
-------------------- -------------------- -------------------- --------------------
.

format FILE =
@<<<<                @<<<<<<<<<<<         @<<<<<<<<<<<         @<<<<<<<<<<<
$event,$node,$delta,$priority
.

See perlform, Perl formats

There's also Perl6::Form, which lets you use Raku-style formats in perl, where the format description is an argument to a function instead of hardcoded into your program; basically, sprintf on steroids.

#!/usr/bin/env perl
use warnings;
use strict;
use feature qw/say/;
use Perl6::Form;

my %hash = (
    '150' => {
             'priority' => 'High',
             'node' => 'Node1',
             'delta' => '00:05:00'
           },
    '170' => {
             'delta' => '00:00:30',
             'node' => 'Node2',
             'priority' => 'Medium'
           }
);

say 'EVENTID              NODE                 DELTA                PRIORITY';
say '-------------------- -------------------- -------------------- --------------------';
for my $key (sort { $a <=> $b } keys %hash) {
    my $text = form
        '{<<<<<<<<<<<<<<<<<<} {<<<<<<<<<<<<<<<<<<} {<<<<<<<<<<<<<<<<<<} {<<<<<<<<<<<<<<<<<<}',
        $key, @{$hash{$key}}{qw/node delta priority/};
    print $text;
}

As an alternative to formatting using Perl code, consider splitting your task into 2 subtasks:

  • use Perl to write the output in a common format, such as a tab-delimited file, then
  • use a different package to format the tab-delimited file.

Consider also whether you need the dashes (-----) after the header. If the dashes are optional, you can pipe the tsv output of perl into *NIX column utility, like so:

perl_script_writing_tsv.pl | column -t -s$'\t'

Here, -t -s$'\t' options split the input on tab instead of the default whitespace, and write nicely aligned output table, similar to yours, except lacking the dashes after the header.


Example:

Code:

#!/usr/bin/env perl

use warnings;
use strict;
use feature qw( say );

# Renamed the hash for clarity: please rename according to your
# knowledge domain.
my %benchmarks_for_eventid = (
    '150' => {
             'priority' => 'High',
             'node' => 'Node1',
             'delta' => '00:05:00'
           },
    '170' => {
             'delta' => '00:00:30',
             'node' => 'Node2',
             'priority' => 'Medium'
           }
);

my $delim = "\t";
my @benchmarks_for_eventid_fields = qw( node delta priority );
    
say join $delim, map { uc } 'eventid', @benchmarks_for_eventid_fields;

# Using numeric 'sort', since eventids are likely integers, not
# strings:
foreach my $eventid ( sort { $a <=> $b } keys %benchmarks_for_eventid ) {
    say join $delim, $eventid,
        map { $benchmarks_for_eventid{ $eventid }{ $_ } }
        @benchmarks_for_eventid_fields;
}

Output of script | column ...:

EVENTID  NODE   DELTA     PRIORITY
150      Node1  00:05:00  High
170      Node2  00:00:30  Medium

Using column to print tab-delimited files nicely aligned:

Example:

perl -le '
print join "\t", qw(col1 col2 col3);
print join "\t", q{r1 c1}, q{r1 c2 loooooooong string}, q{r1 c3};
print join "\t", q{r2 c1}, q{r2 c2}, q{r2 c3};
' | column -t -s$'\t'

Prints:

col1   col2                      col3
r1 c1  r1 c2 loooooooong string  r1 c3
r2 c1  r2 c2                     r2 c3
Related