hahazeeq has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to open multiple log files that are in a directory. So far what I've done is
#!/usr/bin/perl use strict; use warnings; use POSIX 'strftime'; #my $filename = 'IGXLEventLog.3.17.2015.20.25.12.625.log'; my $directory = "/opt/lampp/htdocs/otpms/Data"; opendir (my $dir, $directory) or die "Could not open directory '$direc +tory': $!"; my @list_files; our $output; my %details; for my $filename (@list_files) { open(my $fn, '<', $filename) or die "Could not open file '$filenam +e': $!"; while(my $row = <$fn>) { chomp $row; if ($row =~ /Computer Name:\s*(\S+)/i ) # match computer name +with white space then non white space { $details{tester_name} = $1; } elsif ($row =~ /Operating System:\s*(.*\S)/i ) # match operati +ng system with white space then any word { $details{op_sys} = $1; } elsif ($row =~ /IG-XL Version:\s*([^;]*)/i ) # match ig-xl ver +sion with white space then semi colon { $details{igxl_vn} = $1; } elsif ($row =~ /^([\d.]+)\s+(\S+)(?=\s)/ ) #match slot with wh +ite space and then non white space { push @{$details{slot}}, $1; push @{$details{board_name}}, $2; } } close ($fn); my $timestamp = strftime '%Y-%m-%d.%H:%M:%S', localtime; $output = $timestamp .'.sql'; open(my $fh, '>', $output) or die "Could not create file '@output': $! +"; my $log_time_stamp = (stat($filename))[9]; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime +($log_time_stamp); my $nice_timestamp = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d", $year+1900,$mon+1,$mday,$hour,$min, +$sec); print $nice_timestamp; for (my $i = 0; $i < @{$details{slot}}; $i++) { print {$fh} "INSERT INTO TesterDeviceMatrix.TBL_TESTER_INFO" ."(tester_name, operating_system, version, board_name, config +, date_modified, log_created) " ."VALUES ('$details{tester_name}', '$details{op_sys}', '$detai +ls{board_name}[$i]', " ."'$details{igxl_vn}', '$details{slot}[$i]', '$timestamp', '$n +ice_timestamp');\n"; } close($fh); }
From this I am getting nothing. The basic flow of this program (originally) is to capture the data needed in a log file and create a new file with a .sql extension. However, after adding the @list_files array and the for loop it doesn't seem to create any files. Any help here? I've searched for solutions and I'd prefer the ones that use the open or opendir instead of modules and the other ways.

EDIT 2 I have decided to use readdir as suggested by one of the anonymous monks however, I am getting only one file and all the info captured from other files is combined into this one. Any help?:

This is the working code for one file only, with the name hard coded
#!/usr/bin/perl use strict; use warnings; use POSIX 'strftime'; our $output; my %details; #my $filename = 'IGXLEventLog.3.17.2015.20.25.12.625.log'; my $directory = "/opt/lampp/htdocs/otpms/Data"; opendir (DIR, $directory) or die "Could not open directory '$directory +': $!"; my @files = readdir(DIR); closedir DIR; foreach my $filename (@files) { open(my $fn, '<', "$directory/$filename") or die "Could not open file +'$filename': $!"; while(my $row = <$fn>) { chomp $row; if ($row =~ /Computer Name:\s*(\S+)/i ) # match computer name +with white space then non white space { $details{tester_name} = $1; } elsif ($row =~ /Operating System:\s*(.*\S)/i ) # match operati +ng system with white space then any word { $details{op_sys} = $1; } elsif ($row =~ /IG-XL Version:\s*([^;]*)/i ) # match ig-xl ver +sion with white space then semi colon { $details{igxl_vn} = $1; } elsif ($row =~ /^([\d.]+)\s+(\S+)(?=\s)/ ) #match slot with wh +ite space and then non white space { push @{$details{slot}}, $1; push @{$details{board_name}}, $2; } } close ($fn); my $timestamp = strftime '%Y-%m-%d.%H:%M:%S', localtime; $output = $timestamp .'.sql'; open(my $fh, '>', "$directory/$output") or die "Could not create file +'$output': $!"; my $log_time_stamp = (stat($filename))[9]; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime +($log_time_stamp); my $nice_timestamp = sprintf ( "%04d-%02d-%02d %02d:%02d:%02d", $year+1900,$mon+1,$mday,$hour,$min, +$sec); print $nice_timestamp; for (my $i = 0; $i < @{$details{slot}}; $i++) { print {$fh} "INSERT INTO TesterDeviceMatrix.TBL_TESTER_INFO" ."(tester_name, operating_system, version, board_name, config +, date_modified, log_created) " ."VALUES ('$details{tester_name}', '$details{op_sys}', '$detai +ls{board_name}[$i]', " ."'$details{igxl_vn}', '$details{slot}[$i]', '$timestamp', '$n +ice_timestamp');\n"; } close($fh); }

Replies are listed 'Best First'.
Re: Opening multiple log files
by kcott (Archbishop) on Jun 15, 2015 at 07:00 UTC

    G'day hahazeeq,

    Welcome to the Monastery.

    "From this I am getting nothing."

    You've added checks throughout for failed I/O - this is good. If files weren't created, you would have received messages from these checks. Not posting these is less good: we can't see what went wrong.

    The first problem that leaps out at me is @output.

    This should probably be a scalar, not an array. A lexical variable (my) would probably be better than an alias to a package variable (our).

    Then later in the code

    @output = $timestamp .'.sql'; open(my $fh, '>', @output) or die "Could not create file '@output': $! +";

    becomes

    $output = $timestamp .'.sql'; open(my $fh, '>', $output) or die "Could not create file '$output': $! +";

    There's also potential problems with

    open(my $fn, '<', $filename) ...

    which possibly needs to be

    open(my $fn, '<', "$directory/$filename") ...

    Add temporary print statements to your code to perform your own troubleshooting. Check if you're reading and writing to the correct pathname, as opposed to the correct filename.

    Without seeing your error messages, I'm really just guessing at problems. If you need further assistance, please see these guidelines for what to post such that we can help you most effectively.

    -- Ken

      Thank you Ken. I've corrected the errors as suggested and confirmed that it is writing to the correct path name which is the current path.

      I'm trying to find out how can I execute this exact same script for all of the files(many) in the same directory. The files that I want to include are those with the extension of '.log'. To be specific, the files are log files and I am trying to capture data(succeeded) from each of the file. I have managed to do it for one file now I am trying to do it for each file with a the `.log` extension in the current folder.

        The builtin glob function will probably do what you want. Just make sure you check the "Portability issues" noted in that document.

        Something like:

        my @logfiles = glob "*.log";

        then just iterate @logfiles, processing each in turn.

        Because you said "... all of the files(many) in the same directory ..." you may exceed a maximum size. See GLOB_LIMIT in File::Glob for details.

        If you are likely to approach that limit, a better option (although, a bit more coding) would be to use readdir.

        You'll need to skip everything except normal files with a .log extension. Something like this (untested):

        while(readdir $dh) { next unless -f and /\.log$/; # Process log file here }

        See File Test Operators if you're unfamiliar with -f.

        -- Ken

Re: Opening multiple log files
by Laurent_R (Canon) on Jun 15, 2015 at 06:56 UTC
    Hmm, you have this:
    for my $filename (@list_files)
    running on an uninitialized array, so this for loop will not do anything.

    Then you have this:

    @output = $timestamp .'.sql'; open(my $fh, '>', @output) or die "Could not create file '@output': $! +";
    Filenames should be scalars, not arrays.
      For the second one I've edited it. But for the for loops is the part where I'm trying to open each file in the directory. I'm guessing that's the wrong way, any ideas how I can fix this? So far I've only been able to open one file but the file name is hard coded. I want to be able to run the script for each file in the directory(each of the files have the same format).

        I suspect you're being distracted by having too much information thrown at you at once.

        Examine just this bit of your code:

        my @list_files; for my $filename (@list_files) { }

        Do you see the problem? You just declared @list_files, so by definition it has nothing in it.

        Your loop therefore has nothing to do.

        Perhaps this will help you see the path forward:

        my @list_files = readdir $dir;

Re: Opening multiple log files
by sandy105 (Scribe) on Jun 15, 2015 at 08:43 UTC

    you could read all the .log files like this below code

    opendir(hand,$ARGV[0]); #replace ARGV[0] with your DIR @files = readdir(hand); closedir(hand); foreach(@files){ if(/\.log$/i) { + #if the filename has .log at the end push(@logfiles,$_); } }

    you could then take the logfiles array and do a multithreaded processing like below

    while (scalar @Threads < scalar @logfiles) { @running = threads->list(threads::running); if (scalar @running < $n_process) { + #spawn threads if no of running threads less that MAX proces +ses defined $filename = $logfiles[$fileindex]; my $thread = threads->new( sub { sorterwriter($filename) } +); #spawning worker threads passing filename to su +broutine push (@Threads, $thread); + #pushing no of threads spawned for checking in outer loop my $tid = $thread->tid; print " - starting thread $tid\n"; $fileindex++; } }

    or you could do regular processing by

    $nooffiles = @logfiles; $fileindex=0; while ($fileindex <$nooffiles ) { open hanr ,">", "$logfiles[$fileindex]" or die "could not open logfile + .. $!"; $fileindex++; }
      Hi sandy105 :) readdir is no fun at all, Path::Tiny on the other hand is fun :)
      use Path::Tiny qw/ path /; my @logfiles = path( shift )->children( qr/\.log$/i );

        I rarely use modules because they are not available on the server where i deploy my scripts.

        Since i run windows at home , I rarely write perl at home for personal purposes .But yeah i installed some modules including Path::Tiny the other day . I plan to explore that and some XML parsing modules as well dabble with OO programming in perl , which i haven't done before.Infact all the lagacy scripts i maintain at my company are huge ~5k lines of code and most scripts are similar to other scripts.

Re: Opening multiple log files
by bulrush (Scribe) on Jun 15, 2015 at 11:28 UTC
    There are advantages to using scoped variables, but I have found it simpler to use variables scoped to the procedure in perl 5.8.8, not to a loop or smaller subsection of code. So I declare all my variables at the subroutine level. It makes debugging a WHOLE lot easier. Also, I often have to save the position of a numeric looping variable and pass it to the line of code after the loop (not the case here, but people will ask why I do what I do.)
    sub dosomething
    {
    my($fn,$fh);
    
    }
    
    Also, below you did not declare the DIR variable, and it is preferred to use it as a scalar now I believe. Plus you are using 'use strict'.
    use strict;
    my $DIR;
    opendir ($DIR, $directory) or die "Could not open directory '$directory +': $!";
    
Re: Opening multiple log files
by Anonymous Monk on Jun 15, 2015 at 07:33 UTC
      I have found many solutions using either one of those two ways, however I've done trial and error with my codes and can't seem to be able to create the files. Is there an example you could show me using my codes?
        glob gives you only existing file names. For new files that you want to have created, you have to give the names yourself, e.g. with
        my @list_files = qw/first.log second.log third.log/;
        forget it, obviously hadn't enough coffee yet
Re: Opening multiple log files
by Anonymous Monk on Jun 15, 2015 at 08:39 UTC