File::Temp, binary mode and text files

jarich on 2008-02-26T05:50:50

In my day job, amongst other things, I write training materials and run courses for Perl Training Australia. We've only recently added File::Temp to our course notes, and since we allow our attendees to use either our portable training server (Linux) or their desktop (often Windows) we've hit a problem.

File::Temp's tempfile isn't portable for text files. This is because it opens them using sysopen and the O_BINARY flag (where available). This means that newlines printed to the file aren't converted into the operating system's preferred end of line character(s).

This shouldn't have been a surprise, it's in the documentation:

BINMODE

The file returned by File::Temp will have been opened in binary mode if such a mode is available. If that is not correct, use the binmode() function to change the mode of the filehandle.

Note that you can modify the encoding of a file opened by File::Temp also by using binmode().

However the binmode documentation rightly points out:

For the sake of portability it is a good idea to always use it when appropriate, and to never use it when it isn't appropriate.

I know I can tell the Windows students to write:

binmode($tmp_fh, ":crlf" );

after their call to tempfile(), but it's still not going to be portable. Creating a tempfile doesn't look like it should be a special case, and in my experience, it's usually safe to tell (fairly computer and operating system savvy) students that if it looks portable, it should be.

Is there a layer I can give to binmode to tell it to go back to treating the file as a text file with all the special magic regarding newlines that should happen? I essentially want a: binmode($tmp_fh, ":default");

I can always write File::Temp::Text which doesn't use O_BINARY but that's another, non-standard, module for the students to have to install.

I originally brought this question up on PerlMonks but I'm still hoping for a magic answer that lets me teach students a portable way of handling temporary text files where the file name is available. (Yes, I know about open with an undef filehandle).

Any hints?

Thanks, jarich


PerlIO::eol

phillup on 2008-02-26T17:43:01

Is there a layer I can give to binmode to tell it to go back to treating the file as a text file with all the special magic regarding newlines that should happen? I essentially want a: binmode($tmp_fh, ":default");
Could you use PerlIO::eol?

Do you need portable code? Or portable tempfiles?

dagolden on 2008-02-26T22:21:34

I'm not sure I understand the concern as long as the temporary files are actually temporary -- meaning that they aren't going to persist past the program or be transferred to another machine. So what if the newlines are LF instead of CRLF? As long as Perl treats it all as "\n" when reading and writing, isn't that good enough?

-- dagolden

Re:Do you need portable code? Or portable tempfile

jarich on 2008-02-26T22:41:39

One of my common uses of temporary files is to allow me to make changes to a whole file, know that that all succeeded and then replace said file. For example, to reverse each line in a file I might write:

# Reverses each line in a file
use File::Temp qw(tempfile);
use File::Copy qw(move);
use Fatal qw(open close move);

# Open files
my $filename = shift or die "Usage: $0 filename";
open(my $in, "<", $filename);
my ($tmp_fh, $tmp_name) = tempfile();

# Read line in, remove newline, reverse and print it
while(<$in>) {
        chomp;
        print {$tmp_fh} scalar(reverse($_)), "\n";
}

# Close files.  If no errors here, then everything succeeded!
close $in;
close $tmp_fh;

# Move temp-file over original
move($tmp_name, $filename);

This is a case of using a temporary file - temporarily - but wanting to keep the result permanently. Sure it's a contrived example (one of the exercises from our class in fact), but it should show why not treating \n correctly becomes a problem in the end result.