Perl : Edit the log file data

Viewed 165

I have a log file in a table structured form like this(below) and want to edit same send-id column in it. I new to perl and tried but not able to get what I want.

Code   send_id   dest_id
AW      96       45
BX      65       96

Now here I have to edit that send_id column id's to the names (like 96 = Alex and 65= James) and regenerate the log file like the below format[enter image description here][2].

Code  send_id  dest_id
AW     Alex     45
BX     James    96

I did till here but not getting the desired output.I am reading line by line. I am getting like this

Code  send_id  dest_id
AW     Alex     45
James

Can anyone tell me how to do it?

Thank You.

#!/usr/bin/perl

use warnings;
use strict;

my $log_file = "/home/ajay/Desktop/log.log";
open(Log1,"<$log_file") or die ("Could not open");
open(Log2,">$log2.pl");
while ($a=<Log1>) {
  if ($a =~ /65/){
    $a = "James";
    print Log2"$a\n";
  }
  else {
    print Log2"$a";
  }
}
close Log2;
close Log1;
4 Answers

You can substitute the replacement value in the current line using:

while (my $line = <$Log1>) {
    chomp $line;
    if ($line =~ /65/){
        $line =~ s/65/James/;
    }
    say $Log2 $line;
}

However, a better approach would be to keep a hash of the different replacement values. For example like this:

use 5.22.0;
use strict;
use warnings;
use experimental qw(refaliasing);

my $log_file = "/home/ajay/Desktop/log.log";
my $log2_file = "/home/ajay/Desktop/log2.log";
open( my $Log1, "<", $log_file ) or die "Could not open file $log_file: $!";
open ( my $Log2, ">", $log2_file) or die "Could not open file $log2_file: $!";
while (my $line = <$Log1>) {
    my @fields = split " ", $line;
    next if @fields != 3;
    \my $id = \$fields[1];
    if ( exists $id_map{$id} ) {
        $id = $id_map{$id};
    }
    say $Log2 (join "\t", @fields);
}
close $Log2;
close $Log1;

Update:

To center the output fields you can try:

my %id_map = ("65" => "James", "96" => "Alex");
open( my $Log1, "<", $log_file ) or die "Could not open file $log_file: $!";
my $field_width = 20;
my $num_fields = 3;
open ( my $Log2, ">", $log2_file) or die "Could not open file $log2_file: $!";
my $sep_line = ("-" x ($num_fields * $field_width));
while (my $line = <$Log1>) {
    my @fields = split " ", $line;
    next if @fields != $num_fields;
    \my $id = \$fields[1];
    if ( exists $id_map{$id} ) {
        $id = $id_map{$id};
    }
    say $Log2 $sep_line if $. == 1;
    say $Log2 (join " ", map {align($_, $field_width)} @fields);
    say $Log2 $sep_line if $. == 1;
}
close $Log2;
close $Log1;

sub align {
    my ( $str, $field_width ) = @_;

    my $len = length $str;
    if ($len >= $field_width) {
        return $str;
    }
    else {
        my $left = int(($field_width - $len) / 2);
        my $right = $field_width - $left - $len;
        my $str = (" " x $left) . $str . ( " " x $right );
        return $str;
    }
}

Output:

------------------------------------------------------------
        Code               send_id              dest_id       
------------------------------------------------------------
         AW                  Alex                  45         
         BX                 James                  96         

You're not very clear about the format of the file. It looks like it might be tab-separated, so that's how I'm going to treat it. If that's not the case, then some small tweaks will be needed to this code.

I've also got rid of all the file-handling code. This task should be implemented as a Unix filter - that is, it reads from STDIN and writes to STDOUT. This makes your code both simpler and more flexible.

I've also made it data-driven. It's simple to add new id/name combinations to the %id hash.

#!/usr/bin/perl

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

my %id = (
  96 => 'Alex',
  65 => 'James',
);

while (<>) {
  chomp;
  my @fields = split;
  if (exists $id{$fields[1]}) {
    $fields[1] = $id{$fields[1]};
    say join "\t", @fields;
  } else {
    say;
  }
}

Because it's a Unix filter, you call it using I/O redirection. If we assume we put this code in a file called edittracker then we'd call it like this:

$ edittracker < /home/ajay/Desktop/log.log > log2.log

See this article for far more information about the Unix filter model.

It looks like you have a fixed column format. In that case, unpack is often handy to pull the columns apart. Use the A format specifier and the column widths.

Once you have the columns, simply look in the column you care about and replace its value however you like. Use the same format to put them back together with pack:

use v5.10;

my $header = <DATA>;
print $header;

my %Replacements = (
    96 => 'Alex',
    65 => 'James',
    );

my $format = 'A8 A9 A9';
while( <DATA> ) {
    chomp;
    my( $code, $send_id, $dest_id ) = unpack $format;

    $send_id = $Replacements{$send_id} // $send_id;
    say pack $format, $code, $send_id, $dest_id
    }


__END__
Code   send_id   dest_id
AW      96       45
BX      65       96

Note that packing like this will truncate data that is larger than the width. Here's the output:

Code   send_id   dest_id
AW      Alex     45
BX      James    96

Following code demonstrates one of many possible solutions.

Algorithm

  • read file with pair id name into hash
  • read log file and split fields into hash
  • replace ids with names
  • output updated data
use strict;
use warnings;
use feature 'say';

my %ids;

while( <DATA> ) {            # ids
    next if /^\s*\z/;
    last if /__DATA__/;
    my($id,$name) = split;
    $ids{$id} = $name;
}

while( <DATA> ) {            # log file
    next if /^\s*\z/;
    chomp;
    if( /Code/ ) {
        say join("\t",split);
    } else {
        my %data;
        @data{qw/code send_id dest_id/} = split;
        $data{send_id} = $ids{ $data{send_id} } || $data{send_id};
        $data{dest_id} = $ids{ $data{dest_id} } || $data{dest_id};
        say join("\t",@data{qw/code send_id dest_id/});
    }
}

__DATA__
96  Alex
65  James

__DATA__
Code   send_id   dest_id
AW      96       45
BX      65       96

Output

Code    send_id dest_id
AW      Alex    45
BX      James   Alex
Related