Firstly, appologies if i get any terminology wrong on the upcoming post, this is all still very new to me.
Some background, I have a script that checks our archived network configs for a specific set of settings. As such, the script runs a number of checks, and adds the results to an array for that check.
e.g a check to make sure syslog is configured is added to an array called @internalsyslogerror
after all the checks have run, the arrays for all the checks are added to a hash, with the key being the device name.
note all code has use strict, use warnings
with the following command;
$results{$configs} = [@internalsyslogerror, @bordersyslogerror,
@borderntperror, @borderntperror, @internalntperror,
@bordertacacserror, @internaltacacserror, @enablepasswordchecks,
@internalsnmpkeyserror, @timezoneerror, @configregistererror,
@bannererror, @bootregistererror, @domainnameerror];
The issue im having is what is the most elegant way of extracting this information, i would like to reduce the amount of changes I have to make in order add a new check to the script. Currently i would have to add the additional array to the above code and then add the dereferencing part to the sub that handles that.
Here is currently what i do to de-reference and out put to an array which i then send via an email.
foreach my $k (keys %results) {
push @results, "<b>$k</b><br>";
if (defined $results{$k}[0] ){
push @results, "$results{$k}[0]";
}
if (defined $results{$k}[1] ){
push @results, "$results{$k}[1]";
}
if (defined $results{$k}[2] ){
push @results, "$results{$k}[2]";
}
if (defined $results{$k}[3] ){
push @results, "$results{$k}[3]";
}
if (defined $results{$k}[4] ){
push @results, "$results{$k}[4]";
}
if (defined $results{$k}[5] ){
push @results, "$results{$k}[5]";
}
if (defined $results{$k}[6] ){
push @results, "$results{$k}[6]";
}
if (defined $results{$k}[7] ){
push @results, "$results{$k}[7]";
开发者_JAVA百科 }
if (defined $results{$k}[8] ){
push @results, "$results{$k}[8]";
}
if (defined $results{$k}[9] ){
push @results, "$results{$k}[9]";
}
if (defined $results{$k}[10] ){
push @results, "$results{$k}[10]";
}
if (defined $results{$k}[11] ){
push @results, "$results{$k}[11]";
}
if (defined $results{$k}[12] ){
push @results, "$results{$k}[12]";
}
if (defined $results{$k}[13] ){
push @results, "$results{$k}[13]";
}
}
The question is, can i do what im doing above, but somehow generate the code "on the fly"
Thanks
foreach my $k (keys %results) {
push @results, "<b>$k</b><br>";
for my $result (@{$results{$k}) {
next if (!defined $result);
push @results, $result;
}
}
or even
foreach my $k (keys %results) {
push @results, "<b>$k</b><br>";
push @results, grep { defined $_ } @{$results{$k}};
}
edit: fixed a typo in the last push...
I haven't seen enough of your code to be certain that this snippet doesn't alter existing behavior. But it should be ok. And it definitely improves maintainability:
foreach my $k (keys %results) {
push @results, "<b>$k</b><br>";
foreach my $index ( 0..$#{$results{$k}} ) {
if (defined $results{$k}[$index] ){
push @results, "$results{$k}[$index]";
}
}
}
The above replaces your entire foreach / if construct.
Use a for loop:
for ($i = 0; $i < 14; $i++) {
...
}
It looks to me like this code is.... dysfunctional. Correct me if I am wrong.
$results{$configs} = [@internalsyslogerror, @bordersyslogerror, ... ];
This would only create one long array of scalar values, not an array of arrays. Consider this:
C:\perl>perl -we "@a=qw(1 2 3 a); @g=(11,22,33,44); $b{key}=[@a,@g]; print qq(@{$b{key}},\n); print qq($b{key}[0]);"
1 2 3 a 11 22 33 44,
1
This clearly demonstrates that $b{key}
contains all the values of both @a
and @g
, and that $b{key}[0]
just refers to the first value in the first array, i.e. $a[0]
.
In order to do what you seem to be doing, which is to gather the logs into separate variables, under a certain key, you'd have to use array references:
$results{$config} = [\@internalsyslogerror, \@bordersyslogerror, ...];
The only way I can see your current solution working, is if you have a bug/feature (intended or unintended) earlier in your code whereby the first value in each array contains all the data relevant to that category, e.g.:
$internalsyslogerror[0] = "device A not responding, shutting down.\ndevice A rebooted.\nyada yada\n ....";
If this is the case, then what you are doing is equivalent to:
$results{$config} = [ $internalsyslogerror[0], $bordersyslogerror[0], ...];
But if you should ever get two values in one of the arrays, then your system will be screwed up, and you will have a off-by-one error at the end of your report. Unless you use a dynamic for loop to push
the values, but it will still be messy.
Also, by excluding undefined values, your internal structure - which depends only on order - will be screwed up, so that you will not know if the first value in @results
comes from @internalsyslogerror
or @bordersyslogerror
.
Conclusion:
If you are happy with your current system working well, simply use a for loop as suggested by others. Use a dynamic value instead of absolute. I like FMc's solution (slightly altered):
# Solution by FMc
for my $k (keys %results) {
push @results, "<b>$k</b><br>";
push @results, grep { defined $results{$k}[$_] } 0 .. $#results{$k};
}
If, however, you want to preserve the internal structure, you can't exclude undefined values, and you can't join all the different arrays into one, unless the data is already join in strings in the first array value. So:
$results{$config} = [ \@array1, \@array2, ...];
....
for my $key (keys %results) {
push @results, "<b>$key</b><br>";
my $i=0;
for my $ref (@{$results{$key}]) {
push @results, "Array $i:\n<br>" . (defined @$ref ? "@$ref" : "");
$i++;
}
}
The formatting of the output can be finetuned also. Instead of a string join, e.g. "@array"
, you can do an explicit join: join("<br>\n", @array)
.
If you're not doing anything with the key, this is equivalent:
@results = map { "$_" } grep {; defined } map { @$_ } values %results;
精彩评论