How to get last value (integer/decimal) from file in Perl? - perl

I have few files like mem1.txt, mem2.txt and mem3.txt like below in this case and I want to extract last integer or decimal from it through Perl on Linux machine.
$ cat mem1.txt
{
"status": "success",
"data": {
"resultType": "matrix",
"result": [
{
"metric": {
"__name__": "rbbn_pod_container_resource_limits",
"cluster": "blr-ocp1",
"container_name": "slb-container",
"k8s_pod_name": "sksbx2756x3-slb-57cf86b5-89lx9",
"service_name": "container-system-metrics"
},
"values": [
[
1676562016.975,
"8589934592"
]
]
}
]
}
}
$ cat mem2.txt
{
"status": "success",
"data": {
"resultType": "matrix",
"result": [
{
"metric": {
"__name__": "rbbn_pod_container_resource_limits",
"cluster": "blr-ocp1",
"container_name": "slb-container",
"k8s_pod_name": "sksbx2756x3-slb-57cf86b5-89lx9",
"service_name": "container-system-metrics"
},
"values": [
[
1676562016.975,
"1.02"
]
]
}
]
}
}
$ cat mem3.txt
{
"status": "success",
"data": {
"resultType": "matrix",
"result": [
{
"metric": {
"__name__": "rbbn_pod_container_resource_limits",
"cluster": "blr-ocp1",
"container_name": "slb-container",
"k8s_pod_name": "sksbx2756x3-slb-57cf86b5-89lx9",
"service_name": "container-system-metrics"
},
"values": [
[
1676562016.975,
"4"
]
]
}
]
}
}
I would be expecting the out as below (values without quotes)
8589934592
1.02
4
It will be helpful if I can extract all these data type through single command...

Using gron:
$ ls -1 file{1,2,3}.json;
file1.json
file2.json
file3.json
$ gron file1.json
json = {};
json.data = {};
json.data.result = [];
json.data.result[0] = {};
json.data.result[0].metric = {};
json.data.result[0].metric.__name__ = "rbbn_pod_container_resource_limits";
json.data.result[0].metric.cluster = "blr-ocp1";
json.data.result[0].metric.container_name = "slb-container";
json.data.result[0].metric.k8s_pod_name = "sksbx2756x3-slb-57cf86b5-89lx9";
json.data.result[0].metric.service_name = "container-system-metrics";
json.data.result[0].values = [];
json.data.result[0].values[0] = [];
json.data.result[0].values[0][0] = 1676562016.975;
json.data.result[0].values[0][1] = "8589934592";
json.data.resultType = "matrix";
json.status = "success";
$ for j in file{1,2,3}.json; do
gron "$j" |
awk -F' = ' '
$1 == "json.data.result[0].values[0][1]"{
gsub(/\042|;/, "")
print $2
}
'
done
Output
8589934592
1.02
4

Using perl as requested, using JSON::XS c parser:
#!/usr/bin/perl
use strict; use warnings;
use feature qw/say/;
use File::Slurp;
use JSON::XS;
my #results;
sub fetch_value {
my $file = shift;
my $obj = read_file($file);
$obj = decode_json $obj;
return $obj->{"data"}{"result"}[0]{"values"}[0][1];
}
foreach my $i (1..3) {
push #results, fetch_value("file${i}.json");
}
say join "\n", #results;
Output
8589934592
1.02
4

With jq:
$ jq -r '.data.result|.[0].values|.[0]|.[1]' file{1,2,3}.json
8589934592
1.02
4

Not a perl solution, but a good example of how succinct jq is at extracting data from json files.
$ jq '.data.result[].values[] | last | tonumber' mem*.txt
8589934592
1.02
4

With Parsrs' parsrj.sh:
for x in mem[123].txt; do
# Convert JSON to recode-oriented format
# Absense of -t option outputs every value as a string
# e.g. [0,"1",null,"\"2\""]
# => $[0] 0<0x0a>$[1] 1<0x0a>$[2] null<0x0a>$[3] \"2\"
# if -t was there then every string is quoted and others are not
# e.g. [0,"1",null,"\"2\""]
# => $[0] 0<0x0a>$[1] "1"<0x0a>$[2] null<0x0a>$[3] "\"2\""
# The -e option escapes impolite characters " ", ".", "\t"(<0x09>), "[", "]"
# in key to make the output unambiguous
parsrj.sh -e "$x" |
# Get last line
tail -n 1 |
# Since every output of parsrj.sh shall be formatted to:
# <key> <0x20> <value>
# separate a line with <0x20>, then get 2nd to last columns
# which is equivalent to <value>
cut -d ' ' -f 2-
done

Related

Get key details from the value through grep

I am trying to find the key name as output by matching $country_value variable in grep through the hash I have.
#!/usr/bin/perl -w
use strict;
use warnings;
my $country_value = 1;
my $country = {
'IN' => [
1,
5
],
'US' => [
2,
6
],
'UK' => [
3,
7
]
};
my $country_details = grep { $_ eq $country_value } values %{$country};
print $country_details;
print "\n";
As per the hash, I need to get the output as IN because the value of IN is 1 and the $country_value is 1, which is what I am trying to find out.
But, I get the output as 0 instead of IN.
Can someone please help?
In your code, values returns a reference to an array. You need to dereference that to get a list for grep.
use warnings;
use strict;
my $country_value = 1;
my $country = {
'IN' => [
1,
5
],
'US' => [
2,
6
],
'UK' => [
3,
7
]
};
my $country_details;
for my $name (keys %{$country}) {
if (grep { $_ == $country_value } #{ $country->{$name} }) {
$country_details = $name;
last;
}
}
print $country_details, "\n";
Prints:
IN

Create directory tree in Perl that would comply with Fancytree expected JSON format

How to create directory tree in Perl to comply with Fancytree expected JSON format?
This is the Perl part I came up with, that traverses through given path:
sub get_tree
{
my ($gpath) = #_;
my %r;
use File::Find;
my $c = sub {
my $dir = $File::Find::dir;
my $r = \%r;
my $tdir = $dir;
$tdir =~ s|^\Q$gpath\E/?||;
$r = $r->{$_} ||= {} for split m|/|, $tdir;
};
find($c, $gpath);
return \%r;
}
It returns the following result after JSON encode:
{
"dir3":{
},
"dir1":{
"sub-dir2":{
},
"sub-dir1":{
}
},
"dir2":{
"sub-dir1":{
"sub-sub-dir1":{
"sub-sub-sub-dir1":{
}
}
}
}
}
The expected result for Fancytree to comply with its JSON format is:
[
{"parent": "dir3"},
{"parent": "dir2", "child": [
{"parent": "sub-dir1", "child": [
{"parent": "sub-sub-dir1", "child": [
{"parent": "sub-sub-sub-dir1"}
]}
]}
]},
{"parent": "dir1", "child": [
{"parent": "sub-dir1"},
{"parent": "sub-dir1"}
]}
]
The point is to do it in a single run, without post processing, which would be ideal.
Any help of how to achieve that?
You can try,
use strict;
use warnings;
use Data::Dumper;
sub get_tree {
my ($gpath) = #_;
my %r;
my #root;
use File::Find;
my $cb = sub {
my $tdir = $File::Find::dir;
$tdir =~ s|^\Q$gpath\E/?||;
return if $r{$tdir} or !$tdir;
my ($pdir, $cdir) = $tdir =~ m|^ (.+) / ([^/]+) \z|x;
my $c = $r{$tdir} = { parent => $cdir // $tdir };
if (defined $pdir) { push #{ $r{$pdir}{child} }, $c }
else { push #root, $c }
};
find($cb, $gpath);
return \#root;
}
It uses hash for fast lookup of nodes, and complete directory structure is built atop of #root.
Using recursion instead of File::Find, using Path::Tiny to handle paths:
#!/usr/bin/perl
use warnings;
use strict;
use Path::Tiny;
sub get_tree {
my ($struct, $root, #path) = #_;
for my $child (path($root, #path)->children) {
if (-d $child) {
my $base = $child->basename;
push #$struct, { parent => $base };
my $recurse = get_tree($struct->[-1]{child} = [],
$root, #path, $base);
delete $struct->[-1]{child} unless #$recurse;
}
}
return $struct
}
use Test::More tests => 1;
use Test::Deep;
my $expected = bag({parent => 'dir1',
child => bag(
{parent => 'sub-dir1'},
{parent => 'sub-dir2'})},
{parent => 'dir2',
child => bag(
{parent => 'sub-dir1',
child => bag({
parent => 'sub-sub-dir1',
child => bag({
parent => 'sub-sub-sub-dir1'
})})})},
{parent => 'dir3'});
my $tree = get_tree([], 'paths');
cmp_deeply $tree, $expected, 'same';
I guess the following would produce the structure you wanted.
test.pl
use strict;
use warnings;
use JSON;
sub get_json
{
return JSON->new->latin1->pretty->encode(#_);
}
sub get_tree
{
my ($gpath) = #_;
my (%r,#rr);
use File::Find;
my $c = sub {
my $dir = $File::Find::name;
my $r = \%r;
my $rr = \#rr;
my $tdir = $dir;
$tdir =~ s|^\Q$gpath\E/?||;
my $previtem;
for my $item(split m|/|, $tdir) {
if ($previtem) {
$rr=$r->{$previtem}[1]{child}//=[];
$r= $r->{$previtem}[0]{child}//={};
}
$r->{$item} //= [ { }, $rr->[#$rr]= { parent=>$item } ];
$previtem = $item;
}
};
find($c, $gpath);
return \%r,\#rr;
}
my ($r,$rr) = get_tree($ARGV[0]);
print get_json($rr);
output
[
{
"parent" : "test.pl"
},
{
"parent" : "dir1",
"child" : [
{
"parent" : "sub-dir1"
},
{
"parent" : "sub-dir2"
}
]
},
{
"parent" : "dir2",
"child" : [
{
"parent" : "sub-dir1",
"child" : [
{
"parent" : "sub-sub-dir1"
}
]
}
]
},
{
"parent" : "dir3"
}
]
I've run it: perl test.pl .. So you see 'test.pl' in the output
In case you want to traverse only directories, change the find call to:
find({wanted=>$c, preprocess=> sub { grep { -d $_ } #_; } }, $gpath);
Summarizing, here is the final code, that will produce valid JSON object expected by Fancytree out of the box. Thanks to everyone, who was generous to spend time and provide help.
Perl:
#!/usr/bin/perl
use warnings;
use strict;
=head2 get_tree(path, [depth])
Build sorted directory tree in format expected by Fancytree
=item path - The path from which to start searching.
=item depth - The optional parameter to limit the depth.
=cut
use File::Find;
use JSON;
sub get_tree {
my ( $p, $d ) = #_;
my $df = int($d);
my %r;
my #r;
my $wanted = sub {
my $td = $File::Find::name;
if ( -d $td ) {
$td =~ s|^\Q$p\E/?||;
if ( $r{$td} || !$td ) {
return;
}
my ( $pd, $cd ) = $td =~ m|^ (.+) / ([^/]+) \z|x;
my $pp = $p ne '/' ? $p : undef;
my $c = $r{$td} = {
key => "$pp/$td",
title => ( defined($cd) ? $cd : $td )
};
defined $pd ? ( push #{ $r{$pd}{children} }, $c ) : ( push #r, $c );
}
};
my $preprocess = sub {
my $dd = ( $df > 0 ? ( $df + 1 ) : 0 );
if ($dd) {
my $d = $File::Find::dir =~ tr[/][];
if ( $d < $dd ) {
return sort #_;
}
return;
}
sort #_;
};
find(
{
wanted => $wanted,
preprocess => $preprocess
},
$p
);
return \#r;
}
# Retrieve JSON tree of `/home` with depth of `5`
JSON->new->encode(get_tree('/home', 5));
JavaScript:
$('.container').fancytree({
source: $.ajax({
url: tree.cgi,
dataType: "json"
})
});
I'm using it in Authentic Theme for Webmin/Usermin for File Manager.
Try it on the best server management panel of the 21st Century ♥️

Join from a deep hashref in perl

I have a function called scrape_html what returns an array of deep hashrefs.
The next code prints the correct result:
use 5.014;
use warnings;
for my $table (scrape_html()) {
say join("\t",
$table->{tr}->[0]->{td}->[1],
$table->{tr}->[2]->{td}->[1],
$table->{tr}->[4]->{td}->[1],
);
}
it prints, for example:
r0c1 r2c1 r4c1
I want make it shorter with cleaner row numbers, like the next:
use 5.014;
use warnings;
for my $table (scrape_html()) {
say $table->{tr}->[$_]->{td}->[1] for (qw(0 2 4) );
}
This prints:
r0c1
r2c1
r4c1
The problem is - how to join it? The next code
use 5.014;
use warnings;
for my $table (scrape_html()) {
say join("\t",
(
$table->{tr}->[$_]->{td}->[1] for (qw(0 2 4) )
)
);
}
says:
syntax error at soex.pl line 7, near "] for "
Execution of soex.pl aborted due to compilation errors.
What is the correct syntax?
If someone want a demo of scrape_html
sub scrape_html {
return (
{
'tr' => [
{
'td' => [
'r0c0',
'r0c1'
]
},
{
'td' => [
'r1c0',
'r1c1',
]
},
{
'td' => [
'r2c0',
'r2c1'
]
},
{
'td' => [
'r3c0',
'r3c1'
]
},
{
'td' => [
'r4c0',
'r4c1'
]
},
{
'td' => [
'r5c0',
'r5c1'
]
},
]
}
);
}
You want to use map which returns transformed elements,
say join("\t",
(
map $table->{tr}->[$_]->{td}->[1], qw(0 2 4)
)
);

In Perl, how can I skip an empty key when traversing a hash?

This is my problem, I'm not very knowledgeable in Perl, and I have this function that needs to be fixed.
When this function deviceModelMenu() is called, the CLI displays the following text:
The following models are available
==================================================
1.
2. Cisco1240
3. Catalyst3750
4. Catalyst3650
5. HP2524
The first item is empty, which is wrong, and I need to fix that, the piece of code that displays this menu is:
my $features = shift;
print "=" x 50, "\n";
print "The following models are available\n";
print "=" x 50, "\n";
my $i=1;
foreach (keys %{$features->{features}[0]->{deviceModel}})
{
print "$i. $_ \n";
$i++;
}
If I add the following line:
warn Dumper($features->{features}[0]->{deviceModel});
It dumps this:
$VAR1 = {
'deviceModel' => {
'' => {
'cfg' => []
},
'Cisco1240' => {
'cfg' => [
'cisco1240feature.cfg'
]
},
'Catalyst3750' => {
'cfg' => [
'catalyst3750feature.cfg'
]
},
'Catalyst3650' => {
'cfg' => [
'catalyst3650feature.cfg'
]
},
'HP2524' => {
'cfg' => [
'hp2524feature.cfg'
]
}
}
};
As you may notice, the first item is indeed empty. I added the following line to skip it, and just print the rest of the info:
if ($_ eq '') {
shift;
}
But it doesn't seem to work do what I want. I want to skip the item if it's empty.
Well, shifting #ARGV (implicit argument to shift in main program) nor shifting #_ (implicit argument of shift in a function) are not going to help you, because you are not printing either of them.
You can either:
Not add the '' entry in the first place (depends on how it's generated)
Remove the '' entry before printing:
delete $features->{features}[0]->{deviceModel}->{''};
Don't print the entry:
if($_ eq '') {
next;
}
or
if($_ ne '') {
print "$i. $_ \n";
$i++;
}
foreach (keys %{$features->{features}[0]->{deviceModel}})
{
next unless length($_);
print "$i. $_ \n";
$i++;
}
#!/usr/bin/env perl
use strict; use warnings;
my $devices = {
'deviceModel' => {
'' => { 'cfg' => [] },
'Cisco1240' => { 'cfg' => ['cisco1240feature.cfg' ] },
'Catalyst3750' => { 'cfg' => [ 'catalyst3750feature.cfg' ]},
'Catalyst3650' => { 'cfg' => [ 'catalyst3650feature.cfg' ]},
'HP2524' => { 'cfg' => [ 'hp2524feature.cfg' ]},
}
};
{
my $item = 1;
for my $d (grep length, keys %{ $devices->{deviceModel} }) {
printf "%2d. %s\n", $item++, $d;
}
}
Output:
1. Catalyst3750
2. Cisco1240
3. Catalyst3650
4. HP2524

Perl advice - Taking in a file and changing contents

I have a request for some advice on how to approach this script I want to write in Perl. Basically I have a file that looks like :
id: 1
Relationship: ""
name: shelby
pet: 1
color:4
There are certain keywords such as pet and color that have numbers after then. I want to be able to take in a file and look for these keywords (there are 5 or 6 of them) and then change the number to the word that number corresponds to. That is to say for the keyword "Pet"---> 0 =dog, 1 = cat, 2=fish. And for the keyword "color" 0 = red, 1=blue,2=purple,3=brown,4=white. The script should find and change these numbers. The goal should be an output file that looks like:
id: 1
Relationship: ""
name: shelby
pet: cat
color:white
I've been struggling with how to do this for a while. I looked up online maybe I could do an array of hashes or something but I'm relatively new to Perl and don't know exactly how to even approach this problem.... Any advice would be much appreciated!
Thanks
If we're talking about a small set of values, you could have a hash of arrays:
%lookups = ( pet => [ "dog", "cat", "fish" ],
color => [ "red", "blue", "purple", "brown", "white" ] );
Then, when you're reading the file, check each keyword against the hash. If it has a key with that keyword in it, replace the value in the line you read with the value from the hash.
Usage: script.pl file.txt > output.txt
use strict;
use warnings;
my %tags = (
"pet" => [ qw(dog cat fish) ],
"color" => [ qw(red blue purple brown white) ],
);
my $rx = join '|', keys %tags;
while (<>) {
s/^\s*($rx):\s*(\d+)/$1: $tags{$1}[$2]/;
print;
}
This should do it
use strict;
my $inputFileName = 'E:\test.txt';
my $outputFileName = 'E:\test2.txt';
my %Colors = ( 1 => 'Red' , 2 => 'Green' , 4 => 'Blue' );
my %Pets = ( 1 => 'Dog' , 2 => 'Cat' );
open( IN , "<" , $inputFileName) or die "$inputFileName could not be opened $!";
open( OUT, ">" , $outputFileName) or die "$outputFileName could not be opened $!";
while(<IN>)
{
my $line = $_;
if (/^(\s*pet\s*:\s*)(\d+)/ )
{
$line = $1. $Pets{$2} . "\n";
}
elsif (/\s*^color\s*:\s*(\d+)/ )
{
$line = $1. $Colors{$2} . "\n";
}
print OUT $line;
}
close(IN);
close(OUT);
Using zigdon's suggestion
#!/usr/bin/perl
use strict;
use warnings;
use 5.014;
my %param = (pet => [qw/dog cat fish/],
color => [qw/ red blue purple brown white/],
);
while (<DATA>) {
if (/^(pet|color):\s*(\d)$/) {
print "$1: $param{ $1 }[$2]\n";
}
else {
print;
}
}
__DATA__
id: 1
Relationship: ""
name: shelby
pet: 1
color:4
If there are not many cases, you can try something like this, to be run with perl -p:
if (/^id/)
{
s/\d+/%h=(1=>"dog",2=>"warf",3=>"ee");$h{$&}/e;
}
if (/^other/)
{
s/\d+/%h=(1=>"other_thing",3=>"etc",4=>"etc2");$h{$&}/e;
}
EDIT:
To automate the tests, you can do something like (also taking the idea of hashes from zigdon):
my #interesting_tags = ("color", "pet");
my $regexp = "(" . join("|" , #interesting_tags) . ")";
my %lookups = ( pet => [ "dog", "cat", "fish" ],
color => [ "red", "blue", "purple", "brown", "white" ] );
while (<>)
{
if (/$regexp/)
{
my $element = $&;
s/\d+/$lookups{$element}[$&]/e;
}
}