I am still new to powershell, right now I study foreach with break, I understand the concept, but when it combined with additional variable and break; it confuses me, here is the code:
$i=0
$varZ = (10,20,30,40)
foreach ($var in $varZ)
{
$i++
if ($var -eq 30)
{
break
}
}
Write-Host "30 was found in array position $i"
the result I get showing that variable $i = 3, where $var = 30
but what confuses me, as I understand $i starts with 0 and there is an array $varZ (10,20,30,40), as I understand when $i = 0 $var = 10, hence $i = 3 $var = 40? please correct me and help me understand this code
You are incrementing $i before you do your conditional check; whereas; it should be done after your break statement. Although $i is set to 0 before you begin your loop, you immediately increment by 1 with your statement $i++; thus, when $var is 10, $i which was 0 not become 0+1=1 and so forth.
Related
I want to find indexes of repeated values in array.
E.g.
Input:
$data = #(1, 2, 3, 2, 1)
Output:
$indexes = #(3, 4)
For a different approach, you can use try-catch blocks with a hash table.
$data = #(1, 2, 3, 2, 1)
$hash = #{}
$indexes = for ($i = 0; $i -lt $data.count; $i++ ) {
try {
$hash.add($data[$i],$i)
}
catch {
$i
continue
}
}
# Output
$indexes
3
4
The idea here is to add each value as a key and the corresponding index as a value to the hash table. Since a [hashtable] object can only have unique keys, an exception will be thrown and caught. In the catch block, we just output the index which is ultimately stored in $indexes. The continue statement allows the loop to increment and keep processing.
Algorithmically speaking this solution is almost identical to the already proposed solution. However, it utilizes the more efficient Add() method of [arraylist] rather than rebuilding (+=) an [array] during each iteration. The performance is negligible in this example but could be worth considering in larger data sets. This also opts for the traditional for loop rather than foreach.
$uniqueValues = [collections.arraylist]#()
$indexes = for ($i = 0; $i -lt $data.count; $i++) {
if ($uniqueValues -contains $data[$i]) {
$i
}
else {
[void]$uniqueValues.Add($data[$i])
}
}
# Output
$indexes
3
4
This solution maintains an [arraylist] of unique values ($uniqueValues). Any value that is not unique, its index ($i) is output and stored in $indexes. Uniqueness is determined by using the -contains operator to compare the current value in the $data iteration to what is already in $uniqueValues.
You could also use a Hashtable for this:
$data = 1, 2, 3, 2, 1
$hash = #{}
$indexes = for ($i = 0; $i -lt $data.Count; $i++) {
# the value you store in the $hash in the else block is not important
if ($hash.ContainsKey($data[$i])) { $i } else {$hash[$data[$i]] = $true}
}
$indexes
Result:
3
4
Foreach loop with if inside should do the job:
$data = #(1, 2, 3, 2, 1)
$uniqueValues = #()
$duplicatesIndexes = #()
$data | ForEach-Object {$counter = 0}{
if ($_ -notin $uniqueValues) {
$uniqueValues += $_
} else {
$duplicatesIndexes += $counter
}
$counter++
}
# Output
PS> $duplicatesIndexes
3
4
if i need to process all arrays in a multi dimensional array after skipping the first one how would i go about this?
in this case adding + 5 to each value. what if i want to start at the second array $mdarr[1]<
cls
$mdarr = #()
$i = #()
$ii = #()
$mdarr = #((0,1,2,3,4),(5,6,7,8,9),(10,11,12,13,14))
for ($i = 0; $i -lt $mdarr.Length; ++$i){
for ($ii = 0; $ii -lt $mdarr[$i].Length; ++$i){
$mdarr = $mdarr[$i][$ii] + 5
}
}
write-host $mdarr
there is so much wrong with the above. the result i'm looking for should be:
((0,1,2,3,4),(10,11,12,13,14),(15,16,17,18,19))
how would this be done?
The problem is in updating the array contents. All that's needed is a nested loop to process elements in the inner arrays with appropriate indexing. Like so,
$mdarr = #((0,1,2,3,4),(5,6,7,8,9),(10,11,12,13,14))
for($i = 1; $i -lt $mdarr.Length; ++$i) {
for($j = 0; $j -lt $mdarr[$i].Length; ++$j) {
$mdarr[$i][$j] += 5
}
}
$mdarr[1]
10
11
12
13
14
As why didn't the original work, let's analyze the code and see what was wrong:
# This starts from 1st element (index 0), which was to be skipped. Bug
for ($i = 0; $i -lt $mdarr.Length; ++$i){
# Loop counters $ii and $i are confusing, name is almost same
# What's more, $i is increased instead of $ii. Bug
for ($ii = 0; $ii -lt $mdarr[$i].Length; ++$i){
# This doesn't make sense. It's overwriting the whole
# source array-of-arrays with a single value.
# The array cell was to be updated instead. Bug
$mdarr = $mdarr[$i][$ii] + 5
}
}
To sum up, the idea was there. Due indexing bugs and inappropriate assignment operation, the outcome was wrong. Still, fixing is quite straightforward, as the main logic was okay.
Is it possible to assign two variables the same data from an array in a Perl foreach loop?
I am using Perl 5, I think I came across something in Perl 6.
Something like this:
my $var1;
my $var2;
foreach $var1,$var2 (#array){...}
It's not in the Perl 5 core language, but List::Util has a pairs function which should be close enough (and a number of other pair... functions which may be more convenient, depending on what you're doing inside the loop):
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
use List::Util 'pairs';
my #list = qw(a 1 b 2 c 3);
for my $pair (pairs #list) {
my ($first, $second) = #$pair;
say "$first => $second";
}
Output:
a => 1
b => 2
c => 3
The easiest way to use this is with a while loop that calls splice on the first two elements of the array each time,
while (my($var1, $var2) = splice(#array, 0, 2)) {
...
}
However, unlike foreach, this continually does a double-shift on the original array, so when you’re done, the array is empty. Also, the variables assigned are copies, not aliases as with foreach.
If you don’t like that, you can use a C-style for loop:
for (my $i = 0; $i < #array; $i += 2) {
my($var1, $var2) = #array[$i, $i+1];
...
}
That leaves the array in place but does not allow you to update it the way foreach does. To do that, you need to address the array directly.
my #pairlist = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
for (my $i = 0; $i < #pairlist; $i += 2) {
$pairlist[ $i + 0 ] x= 2;
$pairlist[ $i + 1 ] *= 2;
}
print "Array is #pairlist\n";
That prints out:
Array is feefee 2 fiefie 4 foefoe 6 fumfum 8
You can get those into aliased variables if you try hard enough, but it’s probably not worth it:
my #kvlist = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
for (my $i = 0; $i < #kvlist; $i += 2) {
our ($key, $value);
local(*key, $value) = \#kvlist[ $i, $i + 1 ];
$key x= 2;
$value *= 2;
}
print "Array is #kvlist\n";
Which prints out the expected changed array:
Array is feefee 2 fiefie 4 foefoe 6 fumfum 8
Note that the pairs offered by the List::Pairwise module, which were but very recently added to the core List::Util module (and so you probably cannot use it), are still not giving you aliases:
use List::Util 1.29 qw(pairs);
my #pairlist = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
for my $pref (pairs(#pairlist)) {
$pref->[0] x= 2;
$pref->[1] *= 2;
}
print "Array is #pairlist\n";
That prints out only:
Array is fee 1 fie 2 foe 3 fum 4
So it didn’t change the array at all. Oops. :(
Of course, if this were a real hash, you could double the values trivially:
for my $value (values %hash) { $value *= 2 }
The reasons that works is because those are aliases into the actual hash values.
You cannot change the keys, since they’re immutable. However, you can make a new hash that’s an updated copy of the old one easily enough:
my %old_hash = (
fee => 1,
fie => 2,
foe => 3,
fum => 4,
);
my %new_hash;
#new_hash{ map { $_ x 2 } keys %old_hash } =
map { $_ * 2 } values %old_hash;
print "Old hash is: ", join(" " => %old_hash), "\n";
print "New hash is: ", join(" " => %new_hash), "\n";
That outputs
Old hash is: foe 3 fee 1 fum 4 fie 2
New hash is: foefoe 6 fiefie 4 fumfum 8 feefee 2
A general algorithm for more than 2 variables:
while( #array ){
my $var1 = shift #array;
my $var2 = shift #array;
my $var3 = shift #array;
# other variables from #array
# do things with $var1, $var2, $var3, ...
}
PS: Using a working copy of the array to that it is preserved for use later:
if( my #working_copy = #array ){
while( #working_copy ){
my $var1 = shift #working_copy;
my $var2 = shift #working_copy;
my $var3 = shift #working_copy;
# other variables from #working_copy
# do things with $var1, $var2, $var3, ...
}
}
PPS: another way is to use indexing. Of course, that is a sure sign that the data structure is wrong. It should be an array of arrays (AoA) or an array of hashes (AoH). See perldoc perldsc and perldoc perllol.
my $i = 0;
while( $i < #array ){
my $var1 = $array[ $i++ ];
my $var2 = $array[ $i++ ];
my $var3 = $array[ $i++ ];
# other variables from #array
# do things with $var1, $var2, $var3, ...
}
PPPS: I've been asked to clarify why the data structure is wrong. It is a flatten set of tuples (aka records aka datasets). The tuples are recreated by counting of the number of data for each. But what is the reader constructing the set has a bug and doesn't always get the number right? If, for a missing value, it just skips adding anything? Then all the remaining tuples are shifted by one, causing the following tuples to be grouped incorrectly and therefore, invalid. That is why an AoA is better; only the tuple with the missing data would be invalid.
But an better structure would be an AoH. Each datum would access by a key. Then new or optional data can be added without breaking the code downstream.
While I'm at it, I'll add some code examples:
# example code for AoA
for my $tuple ( #aoa ){
my $var1 = $tuple->[0];
my $var2 = $tuple->[1];
my $var3 = $tuple->[2];
# etc
}
# example code for AoH
for my $tuple ( #aoh ){
my $var1 = $tuple->{keyname1};
my $var2 = $tuple->{key_name_2};
my $var3 = $tuple->{'key name with spaces'};
my $var4 = $tuple->{$key_name_in_scalar_variable};
# etc
}
Here is a module-less way to "loop" by an arbitrary value ($by) and output the resulting group of elements using an array slice:
#!perl -l
#array = "1".."6";
$by = 3; $by--;
for (my $i = 0 ; $i < #array ; $i += $by ) {
print "#array[$i..$i+$by]";
$i++ ;
}
As a one-liner to test (cut and paste to a Unix shell):
perl -E '#array = "1".."6"; $by = 3; $by--;
for (my $i = 0 ; $i < #array ; $i += $by ) {
say "#array[$i..$i+$by]"; $i++ }'
Output:
1 2 3
4 5 6
If you make $by = 2; it will print pairs of numbers. To get at specific elements of the resulting slice access it as an anonymous array: (e.g. [#array[$i..$i+$by]]->[1]).
See also:
How do I read two items at a time in a Perl foreach loop?
Perl way of iterating over 2 arrays in parallel
Some good responses there, including reference to natatime which is quite easy to use. It's easy to implement too - it is essentially a wrapper around the splice solutions mentioned in the responses here.
The following is not the nicest example, but I've been using autobox::Core and made an #array->natatime() "method" ;-) like this:
use autobox::Core ;
sub autobox::Core::ARRAY::natatime {
my ($self, $by) = #_;
my #copy = #$self ;
my #array ;
push #array, [splice (#copy, 0, $by) ] while #copy ;
if ( not defined wantarray ) {
print "#{ $_ } \n" for #array ;
}
return wantarray ? #array : \#array;
}
The #copy array is spliced destructively, but $self (which is how the #array in front of the autobox method -> arrow gets passed to the function) is still there. So I can do:
my #dozen = "1" .. "12" ; # cakes to eat
#dozen->natatime(4) ; # eat 4 at time
my $arr_ref = #dozen->natatime(4) ; # make a reference
say "Group 3: #{ $arr_ref->[2] }" ; # prints a group of elements
say scalar #dozen , " cakes left" ; # eat cake; still have it
Output:
1 2 3 4
5 6 7 8
9 10 11 12
Group 3: 9 10 11 12
12 cakes left
One other approach that also uses a CPAN module (I gave this answer elsewhere but it is worth repeating). This can also be done non-destructively, with Eric Strom's excellent List::Gen module:
perl -MList::Gen=":all" -E '#n = "1".."6"; say "#$_" for every 2 => #n'
1 2
3 4
5 6
Each group of elements you grab is returned in an anonymous array so the individual values are in: $_->[0] $_->[1] ... etc.
You mentioned Perl6, which handles multiple looping values nicely:
my #qarr = 1 .. 6;
my ($x, $y, $z) ;
for #qarr -> $x , $y , $z { say $x/$y ; say "z = " ~ $z }
Output:
0.5
z = 3
0.8
z = 6
For more on the Perl6 approach see: Looping for Fun and Profit from the 2009 Perl6 Advent Calendar, or the Blocks and Statements Synopsis for details. Perhaps Perl 5 will have a similar "loop by multliple values" construct one day - à la perl5i's foreach :-)
I wondered if anyone could shed some light on the issue I am facing when returning values from a multidimensional array through a function:
$ArrayList = #()
function MultiDimensionalArrayTest
{
param(
[array]$ArrayList
)
for($i = 0; $i -lt 1; $i++)
{
$ArrayModify += ,#("Test", "Test")
}
return $ArrayModify
}
$ArrayModify = MultiDimensionalArrayTest
foreach ($item in $ArrayModify)
{
Write-Host $item[0]
}
When the loop is executed once the values returned are:
T
T
However if the for statement is lopped twice, the values returned are:
Test
Test
My aim is to retrieve x amount of values "Test" from the Write-Host $item[0] regardless of how many times the statement is executed
It appears that if two or more rows are captured and returned in the $ArrayModify array, the value is a system.object[], however if looped once, the value "Test, Test" is captured and when Write-Host $item[0] is executed, it will print T.
Any advice would be greatly appreciated.
Not the cleanest way of dealing with it but you need to prevent PowerShell from unrolling the single element array into an array with two elements.
function MultiDimensionalArrayTest{
$ArrayModify = #()
for($i = 0; $i -lt 1; $i++){
$ArrayModify += ,#("Test$i", "Test$i$i")
}
,#($ArrayModify)
}
Using the above function will get the desired output I believe. ,#($ArrayModify) ensures that the array is returned and not unrolled into its elements as you saw above.
$ArrayList = #()
$ArrayList = MultiDimensionalArrayTest
foreach ($item in $ArrayList){$item[0]}
Giving the output for $i -lt 1 in the loop
Test0
Giving the output for $i -lt 2 in the loop
Test0
Test1
Your Output
Concerning your output from your example with $i -lt 1 PowerShell is unrolling the array into a single dimension array with 2 elements "Test" and "Test". You are seeing the letter "T" since all strings support array indexing and will return the character from the requested position.
Other issues with code
Not to beat a dead horse but really look at the other answers and comments as they provide some tips as to some coding errors and anomalies of the code you presented in the question.
First of all I notice several mistakes in your code.
This line $ArrayList = #() is useless since you don't use the $ArrayList variable afterwards.
Regarding the MultiDimensionalArrayTest function, you declared an $ArrayList argument but you never use it in the function.
Finally when this line makes no sense $ArrayModify = MultiDimensionalArrayTest = $item, you probably meant $ArrayModify = MultiDimensionalArrayTest.
This is not a complete answer to your question, since I am not sure what you are trying to achieve, but take a look at this line:
$ArrayModify = MultiDimensionalArrayTest = $item
This makes no sense, since you are calling the function MultiDimensionalArrayTest and passing two arguments to it, which are "=" (powershell assumes this is a string) and $item (null object). Then you assign whatever is returned to $ArrayModify.
The reason "T" is outputted, is because you are outputting the first element of what is at the moment a string. "Test" is outputted when $item is an array of strings.
I am very new to perl and am struggling to get this script to work.
I have taken pieces or perl and gooten them to work as indivual sections but upon trying to blend them together it fails. Even with the error messages that show up I can not find where my mistake is.
The script when working and completed will read an output file and go through it section my section and utilmately generate a new output file with not much more the a heading with some additional text and a value of the amount of lines in that section.
My issues are when it does the looping for each keyword in the array it is now failing with the error message 'Argument "" isn't numeric in array element at'. Perl directs me to a section in the script but I can not see how I am calling the element incorrectly. All the elements in the array are alpha yet the error message is refering to a numeric value.
Can anyone see my mistake.
Thank you
Here is the script
#!/usr/bin/perl -w
use strict;
use warnings;
use diagnostics;
# this version reads each variable and loops through the 18 times put only displays on per loop.
my $NODE = `uname -n`;
my $a = "/tmp/";
my $b = $NODE ;
my $c = "_deco.txt";
my $d = "_deco_mini.txt";
chomp $b;
my $STRING = "$a$b$c";
my $STRING_out = "$a$b$d";
my #keyword = ( "Report", "Last", "HP", "sulog", "sudo", "eTrust", "proftp", "process", "active clusters", "pdos", "syslog", "BNY", "syslogmon", "errpt", "ports", "crontab", "NFS", "scripts", "messages");
my $i = 0;
my $keyword="";
my $x=0;
my $y=0;
my $jw="";
my $EOS = "########################################################################";
my $qty_lines=0;
my $skip5=0;
my $skipcnt=0;
my $keeplines=0;
my #HPLOG="";
do {
print "Reading File: [$STRING]\n";
if (-e "$STRING" && open (IN, "$STRING")) {
# ++$x; # proving my loop worked
# print "$x interal loop counter\n"; # proving my loop worked
for ( ++$i) { # working
while ( <IN> ) {
chomp ;
#if ($_ =~ /$keyword/) {
#if ($_ =~ / $i /) {
#if ($_ =~ /$keyword[ $i ]/) {
if ($_ =~ /$keyword $i/) {
print " $i \n";
$skip5=1;
next;
# print "$_\n";# $ not initalized error when tring to use it
}
if ($skip5) {
$skipcnt++;
print "SKIP LINE: $_\n";
print "Header LINE: $_\n";
next if $skipcnt <= 5;
$skip5=0;
$keeplines=1;
}
if ($keeplines) {
# ++$qty_lines; # for final output
last if $_ =~ /$EOS/;
print "KEEP LINE: $_\n";
# print "$qty_lines\n"; # for final output
push #HPLOG, "$_\n";
# push #HPLOG, "$qty_lines\n";# for final output
}
} ## end while ( <IN> )
} ## end for ( ++$i)
} ## end if (-e "$STRING" && open (IN, "$STRING"))
close (IN);
} while ( $i < 19 && ++$y < 18 );
Here is a sample section or the input file.
###############################################################################
Checking for active clusters.
#########
root 11730980 12189848 0 11:24:20 pts/2 0:00 egrep hagsd|harnad|HACMP|haemd
If there are any processes listed you need to remove the server from the cluster.
############################################################################
This is the output from Pdos log
Please review it for anything that looks like a users may be trying to run something.
#########
This server is not on Tamos
############################################################################
This is the output from syslog.conf.
Look for any entries on the right side column that are not the ususal logs or location.
#########
# #(#)34 1.11 src/bos/etc/syslog/syslog.conf, cmdnet, bos610 4/27/04 14:47:53
# IBM_PROLOG_BEGIN_TAG
# This is an automatically generated prolog.
#
# bos610 src/bos/etc/syslog/syslog.conf 1.11
I truncated the rest of the file
Can anyone see my mistake.
I can see quite a lot of mistakes. But I also see some good stuff like use strict and use warnings.
My suggestion for you is to work on your coding style so that it gets easier for you and others to debug any problems.
Naming variables
my $NODE = `uname -n`;
my $a = "/tmp/";
my $b = $NODE ;
my $c = "_deco.txt";
my $d = "_deco_mini.txt";
chomp $b;
my $STRING = "$a$b$c";
my $STRING_out = "$a$b$d";
Why are some of those names all uppercase and others all lower case? If you are building up a filename, why do you call the variable that holds the filename $STRING?
my #keyword = ( "Report", "Last", "HP", "sulog", "sudo", ....
If you have a list of several keywords, wouldn't it be apt to not chose a singular for the variable name? How about #keywords?
Using temporary variables you don't need
my $NODE = `uname -n`;
my $a = "/tmp/";
my $b = $NODE ;
my $c = "_deco.txt";
chomp $b;
my $STRING = "$a$b$c";
Why do you need $a, $b and $c? The (forgive me) stupid names of those vars are a tell-tale sign that you don't need them. How about this instead?
my $node_name = `uname -n`;
chomp $node_name;
my $file_name = sprintf '/tmp/%s/_deco.txt', $node_name;
Your biggest problem: you have no idea how to use arrays
You are making several drastic mistakes when it comes to arrays.
my #HPLOG="";
Do you want an array or another string? The # says array, the "" says string. I guess you wanted a new, empty array, so my #hplog = () would have been much better. But since there is no need to tell perl that you want an empty array as it will give you an empty one anyway, my #hplog; will do the job just fine.
It took me a while to figure out this next one and I'm still not sure whether I'm guessing your intentions correctly:
my #keyword = ( "Report", "Last", "HP", "sulog", "sudo", "eTrust", "proftp", "process", "active clusters", "pdos", "syslog", "BNY", "syslogmon", "errpt", "ports", "crontab", "NFS", "scripts", "messages");
...
if ($_ =~ /$keyword $i/) {
What I think you are doing here is trying to match your current input line against element number $i in #keywords. If my assumption is correct, you really wanted to say this:
if ( /$keyword[ $i ]/ ) {
Iterating arrays
Perl is not C. It doesn't make you jump through hoops to get a loop.
Just look at all the code you wrote to loop through your keywords:
my $i = 0;
...
for ( ++$i) { # working
...
if ($_ =~ /$keyword $i/) {
...
} while ( $i < 19 && ++$y < 18 );
Apart from the facts that your working comment is just self-deception and that you hard-coded the number of elements in your array, you could have just used a for-each loop:
foreach my $keyword ( #keywords ) {
# more code here
}
I'm sure that when you try to work on the above list, the problem that made you ask here will just go away. Have fun.