1

I have a text file which lists a service, device and a filter, here I list 3 examples only:

service1 device04 filter9
service2 device01 filter2
service2 device10 filter11

I have written a perl script that iterates through the file and should then print device=device filter=filter to a file named according to the service it belongs to, but if a string contains a duplicate filter, it should add the devices to the same file, seperated by semicolons. Looking at the above example, I then need a result of:

service1.txt

device=device04 filter=filter9

service2.txt

device=device01 filter=filter2 ; device=device10 filter=filter11

Here is my code:

use strict;
use warnings qw(all);
open INPUT, "<", "file.txt" or die $!;
my @Input = <INPUT>;

foreach my $item(@Input) {
     my ($serv, $device, $filter) = split(/ /, $item);
     chomp ($serv, $device, $filter);
     push my @arr, "device==$device & filter==$filter";

     open OUTPUT, ">>", "$serv.txt" or die $!;
     print OUTPUT join(" ; ", @arr);
     close OUTPUT;
}

The problem I am having is that both service1.txt and service2.txt are created, but my results are all wrong, see my current result:

service1.txt

device==device04 filter==filter9

service2.txt

device==device04 filter==filter9 ; device==device01 filter==filter2device==device04 filter==filter9 ; device==device01 filter==filter2 ; device==device10 filter==filter11

I apologise, I know this is something stupid, but it has been a really long night and my brain cannot function properly I believe.

  • You have just one filter array where you push everything – MrTux May 09 '18 at 07:11
  • @MrTux but that is doing it `foreach` ? –  May 09 '18 at 07:23
  • What it meant is that you are printing that one array, regardless of what service you got, to the files. So you wind up with all services in all files. Roughly how many different "_service_"s do you have? How large are the files? – zdim May 09 '18 at 23:27
  • @zdim Typically the services will not go over a 100 as new ones are commissioned old ones get decommissioned. I have yet to see a file larger than 10kb, so it is really not large at all. I am purely automatiting this as it is a manual task now to read from DB, manually update each day. With this script I can automate it by DB trigger. –  May 10 '18 at 11:30
  • Thank you for clarification, posted a way to do this – zdim May 10 '18 at 17:48

2 Answers2

1

For each service to have its own file where data for it accumulates you need to distinguish for each line what file to print it to.

Then open a new service-file when a service without one is encountered, feasible since there aren't so many as clarified in a comment. This can be organized by a hash service => filehandle.

use warnings;
use strict;
use feature 'say';

my $file = shift @ARGV || 'data.txt';    
my %handle;

open my $fh, '<', $file or die "Can't open $file: $!";

while (<$fh>) {
    my ($serv, $device, $filter) = split;

    if (exists $handle{$serv}) {
        print { $handle{$serv} } " ; device==$device & filter==$filter";
    }   
    else {
        open my $fh_out, '>', "$serv.txt" or do {
            warn "Can't open $serv.txt: $!";
            next;
        };
        print $fh_out "device==$device & filter==$filter";
        $handle{$serv} = $fh_out;
    }   
}

say $_ '' for values %handle;  # terminate the line in each file

close $_ for values %handle;

For clarity the code prints almost the same in both cases, what surely can be made cleaner. This was tested only with the provided sample data and produces the desired output.

Note that when a filehandle need be evaluated we need { }. See this post, for example.

Comments on the original code (addressed in the code above)

  • Use lexical filehandles (my $fh) instead of typeglobs (FH)

  • Don't read the whole file at once unless there is a specific reason for that

  • split has nice defaults, split ' ', $_, where ' ' splits on whitespace and discards leading and trailing space as well. (And then there is no need to chomp in this case.)

  • Another option is to first collect data for each service, just as OP attempts, but again use a hash (service => arrayref/string with data) and print at the end. But I don't see a reason to not print as you go, since you'd need the same logic to decide when ; need be added.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • Thanks very much for the detailed answer, this makes alot more sense. much appreciated. Sorry for the delay in response, I was on a short break. –  May 14 '18 at 18:42
-1

Your code looks pretty perl4-ish, but that's not a problem. As MrTux has pointed out, you are confusing collection and fanning out of your data. I have refactored this to use a hash as intermediate container with the service name as keys. Please note that this will not accumulate results across mutliple calls (as it uses ">" and not ">>").

use strict;
use warnings qw(all);
use File::Slurp qw/read_file/;
my @Input = read_file('file.txt', chomp => 1);

my %store = (); # Global container
# Capture
foreach my $item(@Input) {
     my ($serv, $device, $filter) = split(/ /, $item);
     push @{$store{$serv}}, "device==$device & filter==$filter";
}
# Write out for each service file
foreach my $k(keys %store) {
    open(my $OUTPUT, ">", "$k.txt") or die $!;
    print $OUTPUT join(" ; ", @{$store{$k}});
    close( $OUTPUT );
}
Stefan Schroeder
  • 651
  • 1
  • 5
  • 11
  • Sorry, there are some issues here. let me go through it and clarify. –  May 09 '18 at 11:21
  • Sorry, I was away on some time off. I had some issues where the results were not as expected, but @zdim's answer solved my issue. Upvoting you answer. –  May 14 '18 at 20:15