Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does repeatedly opening, appending and closing a text file cause the lines to be written in reverse order?

Tags:

perl

Why does the following perl code:

for(open(my $fh, ">>", "test.txt")){
    print $fh "one\n";
}
for(open(my $fh, ">>", "test.txt")){
    print $fh "two\n";
}
for(open(my $fh, ">>", "test.txt")){
    print $fh "three\n";
}

Write the following to test.txt?

three
two
one

Why is the order is getting reversed? My understanding is that each for block will automatically close the file when the block exits. Shouldn't this cause perl to flush any buffers before the the next block starts? I expected this code to open the file, write a line, close the file, then repeat all of those steps two more times. What am I missing?

I tested this with Perl 5.26.1, running on Ubuntu 18.04.3.

(Yes, I know I could easily get the lines to be written in the correct order by just putting all the print statements in the same block. That's not the question here. I want to understand why this behavior is happening.)

For bonus weirdness, when I run the following code:

for my $val (qw/ one two three /) {
    for(open(my $fh, ">>", "test.txt")){
        print $fh "$val\n";
    }
}

It gives me the following output:

one
two
three

This code seems like it should be functionally identical to the previous code. Why is it behaving differently?

like image 432
plasticinsect Avatar asked May 21 '21 22:05

plasticinsect


1 Answers

That's just a complicated version of

open(my $fh, ">>", "test.txt");
print $fh "one\n";
open(my $fh, ">>", "test.txt");
print $fh "two\n";
open(my $fh, ">>", "test.txt");
print $fh "three\n";

Let's make small change to make things more readable and easier to discuss:

open(my $fh1, ">>", "test.txt");
print $fh1 "one\n";
open(my $fh2, ">>", "test.txt");
print $fh2 "two\n";
open(my $fh3, ">>", "test.txt");
print $fh3 "three\n";

This is equivalent because each my creates a new variable.

So what's happening?

open(my $fh1, ">>", "test.txt");  # You create a file handle.
print $fh1 "one\n";               # You write to the file handle's buffer.
open(my $fh2, ">>", "test.txt");  # You create a file handle.
print $fh2 "two\n";               # You write to the file handle's buffer.
open(my $fh3, ">>", "test.txt");  # You create a file handle.
print $fh3 "three\n";             # You write to the file handle's buffer.
# Implicit close($fh3);           # You close the file handle, flushing its buffer.
# Implicit close($fh2);           # You close the file handle, flushing its buffer.
# Implicit close($fh1);           # You close the file handle, flushing its buffer.

Since Perl doesn't guarantee the order in which variable are destroyed, you could easily get the output in any order.

The solution is to flush the handles after printing to them ($fh->flush;, or $fh->autoflush(1);), or close the handles earlier.

{ open(my $fh, ">>", "test.txt");  print $fh "one\n";   }
{ open(my $fh, ">>", "test.txt");  print $fh "two\n";   }
{ open(my $fh, ">>", "test.txt");  print $fh "three\n"; }
like image 197
ikegami Avatar answered Oct 14 '22 20:10

ikegami