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

Good day to you all wise monks. I come to you with a question.
I really, really cannot understand why this code doesn't work the way I expect it to. I have ideas as to why it doesn't, but I just can't seem to correct it.

#!/usr/bin/perl use strict; use warnings; sub mysub{ #Do some clean up before exit. }; my $fh; unless (open ( $fh, '>' , "file.txt")) { mysub(); print "Cannot open file.txt for writing.\n"; exit 0 ; }; my $condition = 0; do { my $text = "Text to be written over file.txt.\n"; print { $fh } "$text"; #... other stuff goes on, eventually changing $condition to 1 } until ($condition); close ($fh); mysub(); exit 1;

This, rather than produce a file named "file.txt" with "Text to be written over file.txt." in it, produces a file named "file.txt" with as many lines of said text as the do...until loop ran.

In a nutshell, it would seem that print is appending rather than clobbering.

I would imagine that there is some issue related to scope of the filehandler variable related to the do...until loop, but I have tried multiple fixes, and none seem to give me the expected behaviour.

Your help will be much appreciated.

Best regards, Mark.

  • Comment on Writing to file with filehandler in variable, scope issue, clobber becomes append
  • Download Code

Replies are listed 'Best First'.
Re: Writing to file with filehandler in variable, scope issue, clobber becomes append
by Athanasius (Archbishop) on Apr 13, 2016 at 14:22 UTC

    Hello Marcool,

    it would seem that print is appending rather than clobbering.

    That’s what print does! The only clobbering in your code occurs when the file is opened for writing: if “file.txt” already exists, its contents will be clobbered by the open($fh, '>', "file.txt") command. But when you print, the file pointer is moved to the position in the file immediately following the last character printed; so the next print begins there. Why did you expect it to work otherwise?

    BTW, it’s usual for a script to return 0 to indicate success and non-zero to indicate failure. Also, it would be better to put the contents of sub mysub into an END block — see BEGIN, UNITCHECK, CHECK, INIT and END.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Oh I never realized that's the way print works! I guess that I though it somehow "passed" its information to the filehander, which dealt with it depending on the "instruction" in the open statement.
      So if I want to clobber the file, I have to use open every time the loop loops do I? Or can I tell print to start from the top of the file somehow? Or empty the file using something other than open() again?

      Thank you for the tip on the exit codes. I suppose I didn't pay too much attention when entering them...
      Also the END issue with the sub. Does that have to do with performance? As in the sub will only be interpreted after everything else, or is there another reason?

        You can tell it to print from the top of the file via the seek command:

        seek $fh, 0, 0;

        But that won’t clobber the file contents; if the text you print is shorter than the text you are overprinting, the original text will still show up at the end of your newly-printed text. If you really need to erase the file contents before printing, then yes, you can close the filehandle and then re-open it for writing.

        Using END isn’t a matter of performance, it’s a means of simplifying and ensuring correctness. It’s simpler, because you don’t have to explicitly call the sub at each exit point in your code; the cleanup code gets called automatically. And suppose you add a third, then a fourth, then a fifth exit point, and somewhere in all that you forget to call the cleanup sub? That’s the sort of bug that can be hard to detect. Better to ensure correctness via code structure than by relying on human memory!

        BTW, I should have said that in Perl the preferred way to exit a script abnormally (i.e., indicating failure of some kind) is to throw an exception using die:

        open(my $fh, '>', 'file.txt') or die "Cannot open file.txt for writing +.\n";

        If the exception isn’t caught, the script will exit, but any END blocks will still be run first.

        Hope that helps,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

        Opening in '>' mode will clobber, of course, but opening inside of a tight loop can be inefficient. Another solution could be to truncate to 0, and then seek to 0 before printing again. But I have no idea if that's actually less inefficient.


        Dave

      Ok, I've edited to close and open the filehandler each time the loop loops (I agree, seems kind of inefficient, that's why I kept it out of the loop at first). Maybe I'll compare that with the truncate and seek option at some point, but for now it's good enough.

      I also get what you mean by using the "END" block. I never knew of it's existence and it does indeed do exactly what I want to do with my cleanup sub. Thanks for that.

      The reason I had quit using die was precisely so I could call my sub. But now I have the END construct I have changed all the "unless..."s back to "or die..."s.

      I'm not sure I would ever have figured out what I had not understood properly without your help. Even the page for "print" on perldoc doesn't really explain this...

      You've been a great help! Thanks again.

      All the best, Mark.

Re: Writing to file with filehandler in variable, scope issue, clobber becomes append
by Discipulus (Canon) on Apr 13, 2016 at 20:03 UTC
    hello Marcool

    you got very good advices and, more important, it seems you understandood them.

    I want just a more general though: read and learn from idiomatic perl (a section of the worth to read modern perl book)

    Also rethink your code: Perl offers you many many way to do the job, but why you want to print different lines to a file clobbering it each time?

    It is not better to choice the line worth to store, and only after print it to the file?

    if you extract the choice logic you can have a sub instead of not so idiomatic (but correct and valid) do {} untill $condition; part.

    Subs can be declared also after their usage in the code, and the program become more readable (more if you choose good names! mysub is not a good one..)

    Consider for example

    print $fh choose_content(); sub choose_content{ my $condition = 0; my $returned=''; while($condition == 0){ $returned = "Line to be printed\n"; #... other stuff goes on, eventually changing $condition to 1 # but probably changing $returned too! } return $returned; }

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.