Perl ASCII variable to Decimal with "." after every letter - perl

I'm making a Perl plugin for Nagios for the F5 load balancer. I have to convert the pool name to a decimal format that matches the OID for SNMP.
my ( $PoolName ) = $ARGV[1];
my ( $rootOIDPoolStatus ) = '1.3.6.1.4.1.3375.2.2.5.5.2.1.2';
For example, $PoolName is "/Common/Atlassian" and I
need to convert that to /.C.o.m.m.o.n./.A.t.l.a.s.s.i.a.n
and then to 47.67.111.109.109.111.110.47.65.116.108.97.115.115.105.97.110
Once that has been converted they would get pulled into one variable
my ( $PoolStatus ) = "$rootOIDPoolStatus.$OIDPoolName"
I have been backwards-engineering other people's Perl plugins for Nagios and this is what someone else is doing, but I couldn't make it work no matter what kind of combinations I was doing. Their $name would be my $PoolName
sub to_oid($) {
my $oid;
my ($name) = $_[0];
return "" if ( ! $name );
$oid = ( length $name ) . '.' . ( join '.', ( map { unpack 'C', $ } ( split '',$name ) ) );
return $oid;
}
Could someone help me to build or understand the Perl logic in order to convert $PoolName to the decimal format I need for the OID?

You seem to be using a string as an index to an SNMP table. The index of a table can be thought of as the row number or row id for that table. Often the index for a table is just a number starting from 1 and increasing with each row the table has. Such a number is encoded in the OID as is, i.e. if the table has 3 columns and two rows, they would have these OIDs:
$base.1 # table
$base.1.1 # table entry
$base.1.1.1.1 # col1, row1
$base.1.1.1.2 # col1, row2
$base.1.1.2.1 # col2, row1
$base.1.1.2.2 # col2, row2
$base.1.1.3.1 # col3, row1
$base.1.1.3.2 # col3, row2
^---index
Sometimes the index is an IP address, a combination of IP:port, or a combination of two IP addresses, especially for IP related tables. An IP address as index would look like this:
$base.1 # table
$base.1.1 # table entry
$base.1.1.1.1.0.0.127 # col1, row "127.0.0.1"
$base.1.1.1.0.0.0.0 # col1, row "0.0.0.0"
$base.1.1.2.1.0.0.127 # col2, row "127.0.0.1"
$base.1.1.2.0.0.0.0 # col2, row "0.0.0.0"
$base.1.1.3.1.0.0.127 # col3, row "127.0.0.1"
$base.1.1.3.0.0.0.0 # col3, row "0.0.0.0"
^^^^^^^---- index
As you can see, the length of the index varies depending on its datatype (there's a dedicated IPV4 datatype).
Sometimes the index is a string (as in your case). When a string is used it must as well be somehow encoded to make up a "row number" for the table. Strings as indexes are encoded character-wise and preceeded by their length, i.e.:
$base.1 # table
$base.1.1 # table entry
$base.1.1.1.2.65.66 # col1, row "AB"
$base.1.1.1.3.120.121.122 # col1, row "xyz"
$base.1.1.2.2.65.66 # col2, row "AB"
$base.1.1.2.3.120.121.122 # col2, row "xyz"
$base.1.1.3.2.65.66 # col3, row "AB"
$base.1.1.3.3.120.121.122 # col3, row "xyz"
^^^^^^^^^^^^^---- index
So "AB" becomes "2.65.66" because length('AB')==2 and ord('A')==65, ord('B')==66. Likewise "xyz" becomes "3.120.121.122".
Your function to_oid does exactly that, although I'd simplify it as follows:
#!/usr/bin/env perl
use strict;
use warnings;
sub to_oid
{
my $string = shift;
return sprintf('%d.%s', length($string), join('.', unpack('C*', $string)));
}
my $rootOIDPoolStatus = '1.3.6.1.4.1.3375.2.2.5.5.2.1.2';
my $PoolName = '/Common/Atlassian';
my $poolname_oid = to_oid($PoolName);
my $complete_oid = "$rootOIDPoolStatus.$poolname_oid";
print $complete_oid, "\n";
Output:
1.3.6.1.4.1.3375.2.2.5.5.2.1.2.17.47.67.111.109.109.111.110.47.65.116.108.97.115.115.105.97.110
|<------- rootOID ----------->|<------------ poolname_oid ----...--->|

my $poolStatus = join '.', $rootOIDPoolStatus, map ord, split //, $poolName;
Not sure what the length() is for in your code, you don't show anything like that in your example.

my $PoolStatus = join('.', $rootOIDPoolStatus, unpack('C*', $PoolName));
or
my $PoolStatus = sprintf("%s.%vd", $rootOIDPoolStatus, $PoolName);

Related

Logical solution for nested for loop

I have a bit of logic drain. I hope I can explain what I am missing and what I want in a coherent manner. Let me know if I have to add a bit more data or information.
I have an Excel spreadsheet which I am trying to load to a database. I have slurped the data into an array of hashes. The data in the array looks like this
$hash_of_excel = [
{
col1 => 'value1',
col2 => 'value2',
col3 => 'value3|value4',
col4 => 'value5|value6|value7',
},
{
col1 => 'value8',
col2 => 'value9',
col3 => 'value10|value11|value12',
col4 => 'value13|value14|value15',
},
{
col1 => 'value16|value17',
col2 => 'value19|value18',
col3 => 'value20',
col4 => 'value21',
}
]
I have a piece of code that walks this data structure to get the values
foreach my $results ( #$hash_of_excel ) {
for my $colname ( sort keys %$results ) {
my #array = split /\|/, $results->{$colname};
foreach my $value ( #array ) {
warn $results->{'col1'}, $results->{'col2'}, $results->{'col3'};
last;
}
}
last if $counter++ == 2;
}
This would result in the same value printing over and over for the number of columns present in each hash (ie 4 in our case).
How can I access different columns for the DBI insert but without having to go through lot of for loops?
Is there a way to check if the value has more than one value and pushing them to array instead of having to get all of them in an array?
Or is this good to hand the database inserts to a subroutine and pass just the required column values in an array?
It's not clear what exactly you want, but your innermost loop is weird: it iterates over #array with $value, but $value isn't used in it - that's why you're getting the same output for all the iterations.
The following loop outputs all the values instead:
foreach my $value (#array){
warn $value;
}
i.e. no $results, no last.

perl script to search for multiple lines from a file with start and end keywords

I am trying to search in a .sql file for sql statement which starts with CREATE TABLE followed by fields values then keywords [TB_DATA and TB_INDX] and ends by ; it in multiple lines
.sql file statement is in multiple lines
-- CREATE TABLE HDTB_COD;
CREATE TABLE HDTB_CODE( IDPK VARCHAR(256) NOT NULL)
IN TB_DATA INDEX
IN TB_INDX;
CREATE TABLE HDTB_RES
(ARTID VARCHAR(256) NOT NULL)
IN TB_DATA INDEX
IN TB_INDX;
-- DROP TABLE HDTB_COD;
CREATE TABLE HDTB_DE ( IDPK VARCHAR(256)
NOT NULL);
-------------output----------------------
CREATE TABLE HDTB_CODE( IDPK VARCHAR(256) NOT NULL)
IN TB_DATA INDEX IN TB_INDX;
CREATE TABLE HDTB_RES(ARTID VARCHAR(256) NOT NULL)
IN TB_DATA INDEX IN TB_INDX;
perl -n -e 'chomp; next if (/^--/);#p=() if /CREATE TABLE/; push #p,$_; if (/IN TB_DATA INDEX IN TB_INDX;/) { print "#p\n"; }' t.sql
How it works
chomp; # remove newlines
next if (/^--/); #skip lines that are SQL comments
#p = () if /CREATE TABLE/; #start of a table definition, clear array #p
push #p, $_; # put current line into array #p
#condition found, print #p
if (/IN TB_DATA INDEX IN TB_INDX;/) { print "#p\n"; }
Here is an example of how to create quick-and-dirty parsing pipelines. Once you understand the basic pattern, it's easy to add more filtering steps (with grep) or transforming steps (with map)
# Slurp entire file.
my $sql = do { local $/ = undef; <> };
# 1. Grab the CREATE TABLE statements.
# 2. Retain only the statements of interest.
# 3. Modify the statements as needed before printing.
print
map { "$_\n" } # 3b. Add trailing newlines.
map { s/\s+/ /g; $_ } # 3a. Normalize whitespace.
grep { /IN TB_INDX/ } # 2b. Filter.
grep { /IN TB_DATA INDEX/ } # 2a. Filter.
$sql =~ /^(CREATE TABLE .+?;)\s*$/gsm; # 1. Grab.

How to force selectrow_array return wrong value?

I remember having problem with DBI method selectrow_array. When i wasn't tidy enough i got back from it not the value of the column i asked, but count of columns (or something unwanted, i can't recall exactly). Now i try to refactor some code and i want to make sure in every possible place, that i get back only expected value. So i try to avoid surprises and find out which the bad behaviour was. From DBI docs i read that this may be really be problematic situation:
If called in a scalar context for a statement handle that has more
than one column, it is undefined whether the driver will return the
value of the first column or the last. So don't do that. Also, in a
scalar context, an "undef" is returned if there are no more rows or if
an error occurred. That "undef" can't be distinguished from an "undef"
returned because the first field value was NULL. For these reasons
you should exercise some caution if you use "selectrow_array" in a
scalar context, or just don't do that.
Still i can't force selectrow_array to return anything but value of the col1 (that's it what i am expecting)
my $query = 'SELECT col1, col2, col3 FROM table WHERE id = 112233';
my ( $c ) = ( $dbh->selectrow_array( $query ) );
my $x = ask_from_db();
my $y = $dbh->selectrow_array( $query );
my $z = ( $dbh->selectrow_array( $query ) );
my #A = $dbh->selectrow_array( $query );
say "C: $c"; # C: col1
say "X: $x"; # X: col1
say "Y: $y"; # Y: col1
say "Z: $z"; # Z: col1
say "A: #A"; # A: col1 col2 col3
sub ask_from_db {
return $dbh->selectrow_array( $query );
}
Every way i ask above, gives me fine result. How should i run the query to get wrong result?
wrong result != col1 value
The difference in outcome will be based on the implementation of the driver.
wantarray ? #row : $row[0]
vs
wantarray ? #row : $row[-1]
You'd use to use a different driver to get a different outcome. That said, I imagine you'll have a hard time finding a driver that doesn't return the first.
If you want to be sure to get the first, use:
( $dbh->selectrow_array( $query ) )[0]
What the documentation means by "it is undefined whether the driver will return the value of the first column or the last" is that the column returned is defined by the database driver and not DBI.
So the Postgres driver may decide to always return the first column whereas the mysql driver may always return the last column, or the column returned might depend on the query.
So don't call selectrow_array is scalar context - always call it in list context:
my #row = $sth->selectrow_array($query)
and you'll avoid all of the issues that the documentation mentions.

Perl DBI insert and select

I want to copy a single record from a table, modify some of the fields in the record and insert into the same table. The table has 90 columns.
Thought of using insert..select in one statement but there are 90 columns and i need to tell the column name in the select query. How can i do it in a better way in perldbi. Pls provide me an example.
Fetch and cache the column names for your table using the NAME statement attribute
my $sth = $dbh->prepare('SELECT * FROM my_table where 1=0');
$sth->execute;
my $cols = $sth->{NAME};
then use $cols to construct your insert...select using some replacement function to inject your modifications in the select.
my %mods_for_column = ( 'foo' => 'foo+10', 'bar' => 'trim(bar)' );
my $inscols = join(',', #$cols);
my $selcols = join(',',
map { exists($mods_for_column($_)) ? $mods_for_column($_) : $_ } #$cols
);
my $sql = "insert into my_table ($inscols) select $selcols from my_table where mumble...";

Perl - CodeGolf - Nested loops & SQL inserts

I had to make a really small and simple script that would fill a table with string values according to these criteria:
2 characters long
1st character is always numeric (0-9)
2nd character is (0-9) but also includes "X"
Values need to be inserted into a table on a database
The program would execute:
insert into table (code) values ('01');
insert into table (code) values ('02');
insert into table (code) values ('03');
insert into table (code) values ('04');
insert into table (code) values ('05');
insert into table (code) values ('06');
insert into table (code) values ('07');
insert into table (code) values ('08');
insert into table (code) values ('09');
insert into table (code) values ('0X');
And so on, until the total 110 values were inserted.
My code (just to accomplish it, not to minimize and make efficient) was:
use strict;
use DBI;
my ($db1,$sql,$sth,%dbattr);
%dbattr=(ChopBlanks => 1,RaiseError => 0);
$db1=DBI->connect('DBI:mysql:','','',\%dbattr);
my #code;
for(0..9)
{
$code[0]=$_;
for(0..9)
{
$code[1]=$_;
insert(#code);
}
insert($code[0],"X");
}
sub insert
{
my $skip=0;
foreach(#_)
{
if($skip==0)
{
$sql="insert into table (code) values ('".$_[0].$_[1]."');";
$sth=$db1->prepare($sql);
$sth->execute();
$skip++;
}
else
{
$skip--;
}
}
}
exit;
I'm just interested to see a really succinct & precise version of this logic.
133 characters - non-strict
use DBI;$d=DBI->connect('DBI:mysql','','',{RaiseError=>1});for$a(0..9){for$b(0..9,'X'){$d->do("insert into table values('$a$b')");}}
152 characters - strict
use strict;use DBI;my$d=DBI->connect('DBI:mysql','','',{RaiseError=>1});for my$a(0..9){for my$b(0..9,'X'){$d->do("insert into table values('$a$b')");}}
Legible version of 152 character string:
use strict;use DBI;
my $d=DBI->connect('DBI:Informix:stores','','',{RaiseError=>1});
foreach my $a (0..9)
{
foreach my $b (0..9, 'X')
{
$d->do("insert into table values('$a$b')");
}
}
Thought process
Given:
create table table(code char(2) not null);
And the Perl:
use strict;
use DBI;
my $d=DBI->connect('DBI:mysql','','',{RaiseError=>1});
my $h=$d->prepare("insert into table(code)values(?)");
foreach my $a (0..9)
{
foreach my $b (0..9, 'X')
{
$h->execute("$a$b");
}
}
I tested with Informix, so the connect string I actually used was "DBI:Informix:stores".
This solution is still readable - and because of the RaiseError, error-proofed (unless you want to add a transaction too).
Code Golfing it, it becomes (182 characters):
use strict;use DBI;my$d=DBI->connect('DBI:mysql','','',{RaiseError=>1});my$h=$d->prepare("insert into table(code)values(?)");for my$a(0..9){for my$b(0..9,'X'){$h->execute("$a$b");}}
The first part can be reduced to
for my $x (0..9) {
for my $y (0..9,'X') {
insert("$x$y");
}
}
I really don't understand what $skip is doing in the second part. I'd have
sub insert {
my $code = shift;
my $sql="insert into table (code) values ('$code');";
my $sth=$db1->prepare($sql);
$sth->execute();
}
You could just do it in sql (for some values of sql):
insert into table (code) select concat(foo, bar)
from (select 0 foo union select 1 union select 2 union select 3 union select 4
union select 5 union select 6 union select 7 union select 8 union select 9)
foo
join (select 0 bar union select 1 union select 2 union select 3 union select 4
union select 5 union select 6 union select 7 union select 8 union select 9
union select 'X')
bar;