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

hi fella monks, i have two files "daily.txt" and "monthly.txt". they are both in CSV format. how will i be able to read the last 3 lines of daily.txt and write those 3 lines in front of monthly.txt then remove those 3 lines from daily.txt. thanks in advance

Replies are listed 'Best First'.
Re: Reading and writing to files
by merlyn (Sage) on Aug 09, 2000 at 14:43 UTC
    damian said:
    i have two files "daily.txt" and "monthly.txt". they are both in CSV format. how will i be able to read the last 3 lines of daily.txt and write those 3 lines in front of monthly.txt then remove those 3 lines from daily.txt. thanks in advance
    (And the posting from Corion fails to rewrite "daily.txt", thus leading me to write the following:)

    Untested code follows:

    local $/; # make undef my @daily = do { local @ARGV = "daily.txt"; <> }; my @monthly = do { local @ARGV = "monthly.txt"; <> }; die "nothing to transfer" unless @daily > 3; my @transfer = splice @daily, -3; # last three of @daily becomes @tran +sfer open OUT, ">daily.txt" or die "Cannot recreate daily.txt: $!"; print OUT @daily; close OUT; open OUT ">monthly.txt" or die "Cannot recreate monthly.txt: $!"; print OUT @transfer, @monthly; close OUT;

    -- Randal L. Schwartz, Perl hacker

      Merlyn, your code truely is beautiful. It is very simple, and effective.

      I am stepping through it to make sure I understand everything. Are the text files read into the arrays in the second and third lines:
      my @daily = do { local @ARGV = "daily.txt"; <> }; my @monthly = do { local @ARGV = "monthly.txt"; <> };
      In the fourth line, you don't have to use #@daily (or whatever that operator is that returns the index of the last element)?

      Again, wow!


      v2: Thank you, Christian. Wow, very beautiful.
        Hi!

        I'll just answer that question, since merlyn has stated often enough that he doesn't have the time to answer all the "easy" questions.

        Lines 2 and 3 put those filenames into the @ARGV array, which has the same effect as putting those filenames in the commandline (@ARGV contains all words in the commandline after the name of the called script.) The operator <> reads from all the filenames on the commandline, or from STDIN if there are no filenames on the commandline. So line 2 and 3 actually do read those files into those arrays without needing an open statement. Beautiful!

        In line 4, the array @daily is used in a scaler context, since it is compared with a scalar (3). In a scalar context an array returns its length, in this case the number of lines in the file. So you don't need that # operator, although you could use it.

        I hope I got that all right, I'm pretty new to perl myself. My compliments to merlyn, that is very short yet very readable code. Perl is beautiful!

        Christian

      OK, I think I understand most of this, including using @ARGV to open the files... But, why is it necessary to recreate daily.txt? Unless something sincerely obfuscated's going on, you're just reading the file, and there shouldn't be any reason to rewrite it. What am I missing?
        One of the parameters of the original problem was to remove the three lines from daily.txt after placing them in monthly.txt. This requires rewriting daily.txt.
        Nothing obfuscated, just gotta follow the directions :-)

        Your Humble Servant,
        -Chuck
Re: Reading and writing to files
by Corion (Patriarch) on Aug 09, 2000 at 12:34 UTC

    Inserting lines into text files can only be done by rewriting the text files. You will have to create a new file, write the three lines into it and then append monthly.txt to that file, and then rename that file to monthly.txt.

    Update: See the below node by Anonymous Monk for stuff I left out and additional hints.

    Some untested code :

    #/usr/bin/perl -w use strict; my $daily = "daily.txt"; my $monthly = "monthly.txt"; # These will hold all lines from the files my @daily_lines; my @monthly_lines; # First extract the last three lines from $daily local *DAILY; open DAILY, "< $daily" or die "Can't open $daily: $!\n"; @daily_lines = <DAILY>; close DAILY; @daily_lines = @daily_lines[-3..-1]; # Now, @daily_lines contains the three last lines from the # file named $daily # Now we will recreate the monthly file : local *MONTHLY; open MONTHLY, "< $monthly" or die "Can't open $monthly : $!\n"; @monthly_lines = <MONTHLY>; close MONTHLY; # And write the new file : open MONTHLY, "> $monthly.tmp"; print MONTHLY join "", @daily_lines; print MONTHLY join "", @monthly_lines; close MONTHLY; # Now we delete $monthly and move our temp file over it : unlink $monthly or die "Couldn't remove old file $monthly : $!\n"; rename( "$monthly.tmp", "$monthly" ) or die "Couldn't rename $monthly. +tmp to $monthly : $!\n";

      That's correct: you cannot add something to the beginning of a file without rewriting the whole file.

      As it seems, the code does not implement one of the required tasks: 'then remove those 3 lines from daily.txt'.

      Of course, this can be fixed:

      #!/usr/bin/perl -w my $daily="daily.txt"; my $weekly="weekly.txt"; my $lines=3; # alternatively, read the names from the command line: # my($daily,$weekly,$lines)=@ARGV; # read the daily file: open DAILY, $daily or die "open $daily: $!"; my @daily=<DAILY>; close DAILY; # read the weekly file: open WEEKLY, $weekly or die "open $weekly: $!"; my @weekly=<WEEKLY>; close WEEKLY; # if the last line of @daily has no \n, this line will # be merged with the first line of @weekly. We are # paranoid and make sure it is there! $daily[-1]=~/\n$/ or $daily[-1].="\n"; # Well. That's good for Unix, not for Windows. You would # have to change "\n" to whatever is appropriate. # move the last $lines lines from the end of @daily to # the beginning of @weekly. Note: # splice(@daily, -$lines) removes and returns the last # $lines lines from @daily; # unshift @weekly, @some_lines adds some lines at the # beginning of @weekly. Together: unshift @weekly, splice(@daily, -$lines); # Write back the weekly file: open WEEKLY, ">$weekly" or die "open >$weekly: $!"; print WEEKLY @weekly; close WEEKLY; # Write back the daily file: open DAILY, ">$daily" or die "open >$daily: $!"; print DAILY @daily; close DAILY;

      You might be interested in the splice and unshift manpages for documentation of the main step of this program.

      By the way: It is possible to append to the end of a file by opening it with open FILE, ">>$name".

      With the truncate function, you can truncate a file (chop off the end leaving the beginning in place). However, I wouldn't recommend that: this function may not be implemented on some systems. Also, it's much simpler to overwrite the file.

Re: Reading and writing to files
by damian (Beadle) on Aug 10, 2000 at 06:13 UTC
    thanks allot guys for helping out there.... i was able to do it this will be a big lesson for me.