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

I've been picking the camel and my brain for a solution to this, but nothing's been helpful nor apparent.

I'm trying to read from a small file (hits), containing only an integer, increment it, and then write it back to the file. Unfortunately, what $hits prints ends up being "1318131813181318" after a few consecutive executions, although I have a feeling this is a result of the mode I used in opening HITSW. I wrote two subroutines because &read_hits() always needs to be executed with the script, and &write_hits() only needs to be executed on certain occasions. I would appreciate your thoughts and solutions to my problem. Thanks in advance.

sub write_hits { open (HITSW, "+< $_[0]"); my $read_hits = <HITSW>; print HITSW $read_hits++; close (HITSW); } sub read_hits { open (HITSR,"$_[0]"); my $read_number = <HITSR>; close (HITSR); return $read_number; } &write_hits("hits"); my $hits = &read_hits("hits"); print "$hits visitors";

Replies are listed 'Best First'.
Re: Reading and incrementing an integer
by IlyaM (Parson) on Jul 31, 2002 at 07:27 UTC
Re: Reading and incrementing an integer
by Dr. Mu (Hermit) on Jul 31, 2002 at 06:49 UTC
    A couple things:
    1. You're using a post-increment ($read_hits++), that increments the variable after it's written to the file.
    2. Between the read and write in write_hits, you need to seek back to the beginning of the file to overwrite the previous value, rather than appending it to what was read.
Re: Reading and incrementing an integer
by Zaxo (Archbishop) on Jul 31, 2002 at 08:06 UTC

    The responses you have from Dr Mu, ph0enix, and dws are all correct, but I have an additional suggestion. Always check system errors and always take a lock on a file you write. Particularly, you must get an exclusive lock on a file you read and then overwrite with an update:

    use Fcntl qw( flock ); sub write_hits { open HITSW, "+< $_[0]" or die $!; flock HITSW, LOCK_EX; my $read_hits = <HITSW>; chomp $read_hits; ++$read_hits; seek HITSW,0,0 or die $!; print HITSW $read_hits, $/ or die $!; close HITSW or die $!; return $read_hits; }
    If this is meant to be a cgi hit counter, the lock is really necessary. Cgi scripts may have more than one instance active. I've added a return value, so &read_hits may not be needed. You may want to eval write_hits() to catch any death-dealing system errors, returning 4*atan2(1,1) or "oodles" after carping appropriately.

    IlyaM suggests File::CounterFile, which takes care of locking, and permits an initial value argument if the count file does not exist.

    After Compline,
    Zaxo

Re: Reading and incrementing an integer
by valdez (Monsignor) on Jul 31, 2002 at 08:04 UTC

    To fully understand what's happening, please read man perlopentut:

    Finally, due to the uncounted millions who cannot be dissuaded +from wasting cycles on useless vanity devices called hit counters, here's how to incre +ment a number in a file safely: use Fcntl qw(:DEFAULT :flock); sysopen(FH, "numfile", O_RDWR | O_CREAT) or die "can't open numfile: $!"; # autoflush FH $ofh = select(FH); $| = 1; select ($ofh); flock(FH, LOCK_EX) or die "can't write-lock numfile: $!"; $num = <FH> || 0; seek(FH, 0, 0) or die "can't rewind numfile : $!"; print FH $num+1, "\n" or die "can't write numfile: $!"; truncate(FH, tell(FH)) or die "can't truncate numfile: $!"; close(FH) or die "can't close numfile: $!";

    Ciao, Valerio

Re: Reading and incrementing an integer
by dws (Chancellor) on Jul 31, 2002 at 06:54 UTC
    I have a feeling this is a result of the mode I used in opening HITSW.

    Indeed. You need to "rewind" the file to overwrite the prior value, and you need to increment the value before you print it.   print HITSW $read_hits++; isn't doing what you think. I'm guessing that you had the value "1318" in your hit file at the time you changed to the code fragment you posted. By not reseting the file position before writing, and not incrementing the value, in effect you're appending "1318" onto the contents of the file, leading to a very long string of digits. You can test that by changing the file contents to "hi, mom\n" and seeing what happens.

    Try making this change:

    my $read_hits = <HITSW>; seek HITSW, 0, 0; print HITSW $read_hits + 1;
Re: Reading and incrementing an integer
by ph0enix (Friar) on Jul 31, 2002 at 06:53 UTC

    You probably want replace old number with new one

    sub write_hits { my $hit_count = &read_hits($_[0]); open (HITSW, '>', "$_[0]"); $hit_count++; print HITSW $hit_count; close (HITSW); }