perl nested while loop - half working - perl

I was hoping that someone might be able to assist me. I'm new to Perl and generally getting some good results from some small scripts I've written, however, I'm stuck on a nested while loop of a new script I'm working on.
The script I've put together performs two mysql select statements, and then places the results into to separate arrays. I then want to check from the first element in the first array against all of the results in the second array. Then move to the second element in the first array and check for against all results in the seconds array and so on.
The goal of the script is to find an IP address in the first array and see which subnets it fits into in the second...
What I find is happening is that the script runs through on only the first element on the first array and all elements on the second array, then stops.
Here is the extract of the perl script below - if anyone could point me int the right direction I would really appreciate it.
my #ip_core_wan_field;
while ( #ip_core_wan_field = $wan_core_collection->fetchrow_array() ) {
my $coreipAddr = #ip_core_wan_field[1];
my #ip_wan_field;
while ( #ip_wan_field = $wan_collection->fetchrow_array() ) {
my $ipAddr = #ip_wan_field[1];
my $network = NetAddr::IP->new( #ip_wan_field[4], #ip_wan_field[5] );
my $ip = NetAddr::IP->new($coreipAddr);
if ( $ip->within($network) && $ip ne $ipAddr ) {
print "$ip IS IN THE SAME subnet as $network \n";
}
else {
print "$coreipAddr is outside the subnet for $network\n\n";
}
}
}

Your sql queries are single pass operations. If you want to loop over the second collection more than once, you need to either cache the values and interate over the cache, or rerun the query.
I would of course advise that you go with the first option using fetchall_arrayref
my $wan_arrayref = $wan_collection->fetchall_arrayref;
while ( my #ip_core_wan_field = $wan_core_collection->fetchrow_array() ) {
my $coreipAddr = #ip_core_wan_field[1];
for my $ip_wan_field_ref (#$wan_arrayref) {
my #ip_wan_field = #$ip_wan_field_ref;
There are of course other ways to make this operation more efficient, but that's the crux of your current problem.

Related

Is it possible to use a variable to store input value(s) and then use the variable to invoke hash table value(s) using PowerShell?

I'm not sure how to explain what I'm trying to do but maybe the code will help you understand.
I might get a few terms wrong, I'm still learning.
I was not unable to find any documentation that would help my case.
I feel like I'm complicating things and I could just use a lot of "if" statements but this seemed to be a way more efficient way (if it's possible) for what I'm trying to accomplish.
I am trying to invoke multiple Hash values with the "foreach" statement using the "read-host" inputs.
#Hash
$AppGroupsHash = #{
1 = "AppADGroup1"
2 = "AppADGroup2"
3 = "AppADGroup3"
4 = "AppADGroup4"
}
$AppGroups = $AppGroupsHash.Values -as [string[]]
#write-host for flair and choices to choose from.
Write-Host "`n1 = AppADGroup1`n2 = AppADGroup1`n3 = AppADGroup1`n4 = AppADGroup1`n " -ForegroundColor Yellow
#User input value(s) for the required task
#Not sure if [string] is necessary in front of $AppGroupsKeys
[string]$AppGroupsKeys = Read-Host "Enter App Hash values (1 - 2 - 3 - 4) (No space, delimit with ';') "
foreach ($AppGroupsKey in $AppGroupsKeys.split(';'))
{
if ($AppGroupsHash.Keys -ccontains $AppGroupsKey)
{
#To test if it will print the values I'm trying to invoke
echo $AppGroupsKey
#This is where I'm getting into trouble
$AppGroupsHash.($AppGroupsKey)
}
}
When I run it. This is what I get, Read-Host Values: 1;3;4 :
#These are the values being echoed but the "$AppGroupsHash.($AppGroupsKey)" does not work
1
3
4
What it should print out:
#Test "echo values"
1
3
4
#The "values" I want
AppADGroup1
AppADGroup3
AppADGroup4
I want to use the variables I've inputted as the Keys that are stored in the hash so that I can invoke those values, these will be eventually used to add users to those groups based on their requirements.
This is what I tried:
foreach ($AppGroupsKey in $AppGroupsKeys.split(';'))
{
if ($AppGroupsHash.Keys -ccontains $AppGroupsKey)
{
#To test if it will print the values I'm trying to invoke
echo $AppGroupsKey
#This is where I'm getting into trouble
write-host "$AppGroupsHash.$AppGroupsKey;"
}
}
Output:
1
System.Collections.Hashtable.1;
2
System.Collections.Hashtable.2;
4
System.Collections.Hashtable.4;
I feel like it's possible but I also feel like I'm missing some sort of secret syntax for this to work.
Is it possible what I want to accomplish or should I try Arrays or just a bunch of "if" statements?

Running a for loop in Powershell

I am new o scripting in powershell and am from a Python background. I want to know if I'm doing this right.
I created this array and want to extract each item one by one
$M365_E3_Grps = ("O365-CHN-DomainUser,O365-Vendor-Exchange-User")
ForEach ($Indiv_Grp in $M365_E3_Grps) {
ForEach ($Indiv_Grp in $M365_E3_Grps) {
`$ADGroup = $Indiv_Grp$ADGroup = $Indiv_Grp`
I want to know if we can extract vals with a for loop like this and assign it to a variable like this.
Construct of your array
Your array is not quite correct and will be populated as a string. To create a string array you will need to quote each item in comma separated list. The parentheses are also not required.
$M365_E3_Grps = "O365-CHN-DomainUser","O365-Vendor-Exchange-User"
Your foreach keyword syntax is however correct, even if the formatting in your question was slightly off.
foreach ($Indiv_Grp in $M365_E3_Grps) {
# Assigning $Indiv_Grp to $ADGroup here is kind of redundant since
# the value is already assinged to $Indiv_Grp
$Indiv_Grp
}

How would you make abstracted nested dictionary/hashtable objects readable (style)?

I'm parsing a database table exported into csv where there are embedded fields in what is essentially a memo field.
The database also contains version history, and the csv contains all versions.
Basic structure of the data is Index(sequential record number),Reference(specific foreign key), Sequence (order of records for a given reference), and Data (the memo field with the data to parse).
You could think of the "Data" field as text documents limited to 80 chars wide and 40 chars deep, and then sequenced in the order they would print. Every record entry is assigned an ascending index.
For reference, $myParser is a [Microsoft.VisualBasic.FileIO.TextFieldParser], so ReadFields() returns a row of fields as an array/list.
My ultimate question is, how can this be formatted to be more intuitive to the reader? Below code is powershell, i'd be interested in answers relating to C# also,as it's something of a language agnostic style problem, though i think get/set would trivialize this to some degree.
Consider the following code (an insert/update routine in a 2 deep nested dictionary/hash):
enum cmtField
{
Index = 0
Sequence = 1
Reference = 2
Data = 4
}
$myRecords = [System.Collections.Generic.Dictionary[int,System.Collections.Generic.Dictionary[int,string]]]::new() #this could be a hash table, but is more verbose this way
While($true) #there's actually control here, but this provides a simple loop assuming infinite data
{
$myFields = $myParser.ReadFields() #read a line from the csvfile and return an array/list of fields for that line
if(!$myRecords.ContainsKey($myFields[[cmtField]::Reference])) #if the reference of the current record is new
{
$myRecords.Add($myFields[[cmtField]::Reference],[System.Collections.Generic.Dictionary[int,CommentRecord]]::new()) #create tier 1 reference index
$myRecords[$myFields[[cmtField]::Reference]].add($myFields[[cmtField]::Sequence],$myFields[[cmtField]::Data]) #create tier 2 sequence reference and data
}
else #if the reference aklready exists in the dictionary
{
if(!$myRecords[$myFields[[cmtField]::Reference]].ContainsKey($myFields[[cmtField]::Sequence])) #if the sequence ID of the current record is new
{
$myRecords[$myFields[[cmtField]::Reference]].Add($myFields[[cmtField]::Sequence],$myFields[[cmtField]::Data]) #add record at [reference][sequence]
}
else #if the sequence already exists for this reference
{
if($myRecords[$myFields[[cmtField]::Reference]][$myFields[[cmtField]::Sequence]].Index -lt $myFields[[cmtField]::Index]) #if the index of the currently read field is higher than the store index, it must be newer
{
$myRecords[$myFields[[cmtField]::Reference]][$myFields[[cmtField]::Sequence]] = $myFields[[cmtField]::Data] #replace with new data
}
#else discard currently read data (do nothing
}
}
}
Frankly, trying to make this readable both makes my head hurt and my eyes bleed a little. It only gets messier and messier the deeper the dictionary goes. I'm stuck between the bracket soup and no self-documentation.
My ultimate question is, how can this be formatted to be more intuitive to the reader?
That... ultimately depends on who "the reader" is - is it your boss? Your colleagues? Me? Will you use this code sample to teach programming to someone?
In terms of making it less "messy", there are a couple of immediate steps you can take.
The first thing I would change to make your code more readable, would be to add a using namespace directive at the top of the file:
using namespace System.Collections.Generic
Now you can create nested dictionaries with:
[Dictionary[int,Dictionary[int,string]]]::new()
... as opposed to:
[System.Collections.Generic.Dictionary[int,System.Collections.Generic.Dictionary[int,string]]]::new()
The next thing I would reduce is repeated index access patterns like $myFields[[cmtField]::Reference] - you never modify $myFields after initial assignment at the top of the loop, so there's no need to delay resolution of it.
while($true)
{
$myFields = $myParser.ReadFields()
$Reference = $myFields[[cmtField]::Reference]
$Data = $myFields[[cmtField]::Data]
$Sequence = $myFields[[cmtField]::Sequence]
$Index = $myFields[[cmtField]::Index]
if(!$myRecords.ContainsKey($Reference)) #if the reference of the current record is new
{
$myRecords.Add($Reference,[Dictionary[int,CommentRecord]]::new()) #create tier 1 reference index
$myRecords[$Reference].Add($Sequence,$Data) #create tier 2 sequence reference and data
}
else
{
# ...
Finally, you can simplify the code vastly by abandoning nested if/else statements, and instead just break it down into a succession of steps that has to pass one by one, and you end up with something like this:
using namespace System.Collections.Generic
enum cmtField
{
Index = 0
Sequence = 1
Reference = 2
Data = 4
}
$myRecords = [Dictionary[int,Dictionary[int,CommentRecord]]]::new()
while($true)
{
$myFields = $myParser.ReadFields()
$Reference = $myFields[[cmtField]::Reference]
$Data = $myFields[[cmtField]::Data]
$Sequence = $myFields[[cmtField]::Sequence]
$Index = $myFields[[cmtField]::Index]
# Step 1 - ensure tier 1 dictionary is present
if(!$myRecords.ContainsKey($Reference))
{
$myRecords.Add($Reference,[Dictionary[int,CommentRecord]]::new())
}
# (now we only need to resolve `$myRecords[$Reference]` once)
$record = $myRecords[$Reference]
# step 2 - ensure sequence entry exists
if(!$record.ContainsKey($Sequence))
{
$record.Add($Sequence, $Data)
}
# step 3 - handle superceding comment records
if($record[$Sequence].Index -lt $Index)
{
$record[$Sequence] = $Data
}
}
I personally find this easier on the eyes (and mind) than the original if/else approach

Loop through multiple array in powershell

I have 2 arrays here one contains the servername and other contains the IP.
I need to loop through them and create a key value pair like below for each server
server1:ip1
server2:ip2
I have written below code, but the problem is if i debug the code using F11, it is working fine, but i don't it gives some error which is different every time.
so feeling like it is not that reliable piece to continue.
$NewDNSEntryName = $DNSEntryName.Split(",")
$DNSIPs = $DNSIP.Split(",")
if($DNSEntryName -match "," -or $DNSIP -match ",")
{
0..($NewDNSEntryName.Count - 1) | ForEach-Object {
$fullName=""
$fullName += #("$($NewDNSEntryName[$_]):$($DNSIPs[$_])")
This is the line where i am facing trouble
0..($NewDNSEntryName.Count - 1) | ForEach-Object
Please let me know why this code is behaving like this else any alternate idea is appreciated
Assuming each item in each list corresponds with each other exactly, you can use a for loop and loop through the array indexes.
$NewDNSEntryName = $DNSEntryName.Split(",")
$DNSIPs = $DNSIP.Split(",")
for ($i = 0; $i -lt $DNSIPs.count; $i++) {
"{0}:{1}" -f $NewDNSEntryName[$i],$DNSIPs[$i]
}
For the code above to work, $DNSEntryName and $DNSIP must be single strings with commas between names and IPs. If $DNSEntryName and $DNSIP are already lists or arrays, something else will need to be done.
In your attempt, technically, your logic should work given everything written above is true. However, $fullName is emptied at every single iteration, which may produce undesirable results.

What does #data actually contain in PDF::Report::Table $table_write->addTable(#data);?

I think I've got the gist of creating a table using Perl's PDF::Report and PDF::Report::Table, but am having difficulty seeing what the 2-dimensional array #data would look like.
The documentation says it's a 2-dimensional array, but the example on CPAN just shows an array of arrays test1, test2, and so on, rather than the example showing data and formatting like $padding $bgcolor_odd, and so on.
Here's what I've done so far:
$main_rpt_path = "/home/ics/work/rpts/interim/mtr_prebill.rpt";
$main_rpt_pdf =
new PDF::Report('PageSize' => 'letter', 'PageOrientation' => 'Landscape',);
$main_rpt_tbl_wrt =
PDF::Report::Table->new($main_rpt_pdf);
Obviously, I can't pass a one dimensional array, but I have searched for examples and can only find the one in CPAN search.
Edit:
Here is how I am trying to call addTable:
$main_rpt_tbl_wrt->addTable(build_table_writer_array($pt_column_headers_ref, undef));
.
.
.
sub build_table_writer_array
# $data -- an array ref of data
# $format -- an array ref of formatting
#
# returns an array ref of a 2d array.
#
{
my ($data, $format) = #_;
my $out_data_table = undef;
my #format_array = (10, 10, 0xFFFFFF, 0xFFFFCC);
$out_data_table = [[#$data],];
return $out_data_table;
}
and here is the error I'm getting.
Use of uninitialized value in subtraction (-) at /usr/local/share/perl5/PDF/Report/Table.pm line 88.
at /usr/local/share/perl5/PDF/Report/Table.pm line 88
I cannot figure out what addTable wants for data. That is I am wondering where the formatting is supposed to go.
Edit:
It appears the addData call should look like
$main_rpt_tbl_wrt->addTable(build_table_writer_array($pt_column_headers_ref), 10,10,xFFFFFF, 0xFFFFCC);
not the way I've indicated.
This looks like a bug in the module. I tried running the example code in the SYNOPSIS, and I got the same error you get. The module has no real tests, so it is no surprise that there would be bugs. You can report it on CPAN.
The POD has bugs, too.
You increase your chances of getting it fixed if you look at the source code and fix it yourself with a patch.