http://qs321.pair.com?node_id=464297

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

Esteemed monks,

The script I'm working on is supposed to read a file, which contents is dynamically changed (/proc/stat). I need to re-read the first line about every second. That makes me wonder:

- How do I move the access pointer to the beginning of the file (and can I do it at all in a text mode)?

- Do I need to close and reopen the file every time I want to read the updated content?

Any pointers are appreciated...

--------------------------------
An idea is not responsible for the people who believe in it...

Replies are listed 'Best First'.
Re: Reopen file when contents changed?
by blazar (Canon) on Jun 07, 2005 at 13:39 UTC
    You may simply want seek. I'm not really sure if it is well suited for that particular file, which may not be the case. It' worth trying in any case.

    Incidentally under *NIX there's not strictly speaking anything like "text mode".

    Update: I didn't want to test this myself, but in the end I did and it seems to work just fine:

    #!/usr/bin/perl use strict; use warnings; open my $fh, '<', '/proc/stat' or die $!; { print scalar <$fh>; seek $fh, 0, 0; sleep 1; redo; } __END__
      That code worked for me and generated output has follows:
      cpu 757611 0 128970 6183389 cpu 757611 0 128970 6183507 cpu 757611 0 128970 6183608 cpu 757612 0 128970 6183708 cpu 757612 0 128970 6183809
Re: Reopen file when contents changed?
by Joost (Canon) on Jun 07, 2005 at 13:40 UTC
Re: Reopen file when contents changed?
by merlyn (Sage) on Jun 07, 2005 at 14:26 UTC
    I'm not sure about things in /proc Normally, if you stat the file and record dev/ino/ctime, and then read the data, you can simply go into a sleep-1 loop until the stat of dev/ino/ctime changes.

    For example:

    my @base = stat($FILE); my $base = "@base[0,1,10]"; # dev/ino/ctime while (1) { ... read $FILE here, and process it ... while (1) { my @new = stat($FILE); my $new = "@new[0,1,10]"; # current dev/ino/ctime if ($base ne $new) { # changed $base = $new; # reset last; # restart outer loop } sleep 1; # delay because no change } }

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

Re: Reopen file when contents changed?
by bofh_of_oz (Hermit) on Jun 07, 2005 at 14:47 UTC
    After reading all these *very* helpful advices, here's what i've got:

    use strict; use warnings; while() { my @loads; my $i = my $cpuload = 0; open(INFIL,"< /proc/stat") || die("Unable To Open /proc/stat\n"); <INFIL> =~ /^cpu\s+(\d+)\s+(\d+)\s+(\d+).*/; @loads = ($1, $2, $3); sleep 1; seek INFIL, 0, 0; <INFIL> =~ /^cpu\s+(\d+)\s+(\d+)\s+(\d+).*/; foreach ($1, $2, $3) { $cpuload += $_ - $loads[$i++]; } close(INFIL); print "$cpuload\n"; }

    Tie::File looks interesting, so that might not be the final version ;)

    Thanks a lot folks!

    --------------------------------
    An idea is not responsible for the people who believe in it...

      use strict; use warnings;
      Good!
      while()
      I think that
      while(1)
      is more customary. To be fair I thought yours wouldn't have worked at all, but to be sure I tried and it did!
      { my @loads; my $i = my $cpuload = 0; open(INFIL,"< /proc/stat") || die("Unable To Open /proc/stat\n");
      I, for one (but I'm not the only one!), recommend using the three args form of open. Also, low precedence or is better suited for flow control and you may benefit from including $! in the error message.
      <INFIL> =~ /^cpu\s+(\d+)\s+(\d+)\s+(\d+).*/; @loads = ($1, $2, $3);
      This is overly complex for what it does, IMHO. I would probably do something like
      my @loads = (<$fh> =~ /\d+/g)[0,1,2];
      instead. Of course this is not strictly equivalent to yours. Indeed it may be worth to check that the line is the correct one. In that case you may still do something like this:
      local $_=<$fh>; (warn "something wrong!\n"), next unless /^cpu\b/; my @loads = (/\d+/g)[0..2];
      sleep 1; seek INFIL, 0, 0; <INFIL> =~ /^cpu\s+(\d+)\s+(\d+)\s+(\d+).*/; foreach ($1, $2, $3) { $cpuload += $_ - $loads[$i++]; } close(INFIL);
      So you want to iterate over the two lists in parallel. Now this is a situation in which some Perl6 features would turn out to be very handy!

      However, and this is not strictly perl-specific, instead of getting the same info twice per cycle, you may get it once only, and take a "backup" version of it to compare with.

        Side discussion: why

        while () { ... }

        is interpreted as

        while (1) { ... }

        I can't find anything relevant in perldoc perlsyn.

        $ perl -MO=Deparse -e 'while () {print "tick!\n"}' while (1) { print "tick!\n"; }

        Update: Below is the deparse from perl 5.6.1. Above was 5.8.

        $ perl561 -MO=Deparse -e 'while () {print "tick!\n"}' for (;;) { print "tick!\n"; }

        Update 2: See more discussion in thread while ()

Re: Reopen file when contents changed?
by joelnackman (Beadle) on Jun 07, 2005 at 14:12 UTC
    I'm not completely sure, but I think you should take a look at the Tie::File module. It allows you to treat a file like an array. Then you could just keep reading the first item in the array. Here's what it looks like:
    use Tie::File; ... tie @data, Tie::File, $filename or die "Can't tie to $filename :$!\n";
    The @data array would then contain the contents of the file, one entry per line. You could then just read $data[0], which would be the first line of the file. I think that it would update dynamically.

    Tie::File Documentation
Re: Reopen file when contents changed?
by salva (Canon) on Jun 07, 2005 at 13:59 UTC
    just once per second? performance is not going to be an issue: use the aproach that seems simpler for you.

    I would go for...

    sub first_line_from_file { my $fn = shift; open my $file, "<", $fn or die "unable to open file $fn"; scalar <$file> }

    update: added the scalar in front of <$file>. blazar, thanks for pointing it out.

      sub first_line_from_file { my $fn = shift; open my $file, "<", $fn or die "unable to open file $fn"; <$file> }
      To return the first line I'd say
      scalar <$file>;
      (The semicolon is cosmetic. But I consider it good style)
Re: Reopen file when contents changed?
by Fletch (Bishop) on Jun 07, 2005 at 14:17 UTC

    If you're on a system that supports it it might be more efficient to see if you could use SGI::FAM, or look into wrapping kqueue on a BSD box using Inline::C. That'd let the OS notify you just when it changes.

    Update: Oh, just read that you're reading stuff from /proc; never mind as my suggestions probably won't work there.

    --
    We're looking for people in ATL

      Nothing much to do with the original question ...
      I hadn't found about about kqueue before until I browsed this node - looks very useful.

      Thanks!
Re: Reopen file when contents changed?
by Thargor (Scribe) on Jun 07, 2005 at 16:10 UTC
    I am not sure if it would work but couldn't you just create a pointer to the first line of the file when you initially read it in and just leave it static?
      Huh? Pointer? static? What are you talking about?
        When you open a file you have a pointer that points at the first line of the file. So I was just suggesting the OP might want to try just dupelicating that pointer and not changing what it points to while the original file pointer loops through the rest of the lines of the file reading in the data. I am slightly new to perl not sure if it would work. Hence it would be a static pointer, one that does not change, which is all the OP needs a constant pointer to the first line of the file. This would only be usefull if he isn't adding any information to the top of the file because the pointer would then not be pointing to the correct line.
Re: Reopen file when contents changed?
by thcsoft (Monk) on Jun 07, 2005 at 14:09 UTC
    if you're on a unix/linux system, you'ld be better off with a shell script.

    language is a virus from outer space.
      Possibly, but AFAIK forking a shell requires resources, which will impact the measurements; opening a file will not...

      --------------------------------
      An idea is not responsible for the people who believe in it...

        Forking anything would require resources... There is no need to fork just to read the contents of a file. But anyway, I think keeping the file open and just seek(0) is easier than opening the file everytime. I don't know how much impact open() would generate on load, but I do think seek(0) would cause even less impact.
      why?