There was a subtle API change in File::Temp 0.23 that improves consistency, but might break old, buggy code.
Prior to 0.23, here was the calling signature for the functional and object oriented interfaces for File::Temp (with some creative spacing to show the problem):
# functional my ( $fh, $filename ) = tempfile( $template, %options ); my $tempdir = tempdir ( $template, %options ); # object oriented my $tmp = File::Temp->new ( %options ); my $dir = File::Temp->newdir ( $template, %options );
Notice how new()
doesn’t take a template argument. Instead, you’re supposed to pass it as an option in the %options
hash: TEMPLATE => 'tempXXXXX'
.
Frankly, this interface sucks. There are too many ways to get confused or do it wrong:
- What happens if you pass a leading template to
new()
? - What happens if you leave off the leading template for
newdir()
? - What happens if you pass a TEMPLATE option to
newdir()
,tempfile()
ortempdir()
? - What happens if you call
tempfile()
ortempdir()
as methods?
A test program 🔗︎
Here’s a little test program to try out some variations. Notice that a leading template argument is ‘arg_XXXX’ and a TEMPLATE option is ‘opt_XXXX’, so we can see which takes precedence if we try with both:
#!/usr/bin/env perl use v5.10; use strict; use warnings; use File::Temp qw/tempfile tempdir/; my @cases = ( # documented API q{tempfile ('arg_XXXX' )}, q{tempdir ('arg_XXXX' )}, q{File::Temp->new ( TEMPLATE => 'opt_XXXX')}, q{File::Temp->newdir ('arg_XXXX' )}, # variations with both arg and TEMPLATE q{tempfile ('arg_XXXX', TEMPLATE => 'opt_XXXX')}, q{tempdir ('arg_XXXX', TEMPLATE => 'opt_XXXX')}, q{File::Temp->new ('arg_XXXX', TEMPLATE => 'opt_XXXX')}, q{File::Temp->newdir ('arg_XXXX', TEMPLATE => 'opt_XXXX')}, # newdir called like new q{File::Temp->newdir ( TEMPLATE => 'opt_XXXX')}, # functions called as methods q{File::Temp->tempfile('arg_XXXX' )}, q{File::Temp->tempdir ('arg_XXXX' )}, q{File::Temp->tempfile('arg_XXXX', TEMPLATE => 'opt_XXXX')}, q{File::Temp->tempdir ('arg_XXXX', TEMPLATE => 'opt_XXXX')}, q{File::Temp->tempfile( TEMPLATE => 'opt_XXXX')}, q{File::Temp->tempdir ( TEMPLATE => 'opt_XXXX')}, ); for my $c ( @cases ) { my @result = eval $c; my $err = $@; $err =~ s/\n.*//ms; say $c; say " " . ( $result[-1] ? "Got $result[-1]" : $err ) . "\n"; }
Results with File::Temp 0.22 🔗︎
Here are the result running under File::Temp 0.22 for the documented API:
tempfile ('arg_XXXX' ) Got arg_Y9B5 tempdir ('arg_XXXX' ) Got arg_Joq0 File::Temp->new ( TEMPLATE => 'opt_XXXX') Got opt_p9I5 File::Temp->newdir ('arg_XXXX' ) Got arg_PmNf
That’s just as we expect.
Now, let’s try those odd cases. First, calling everything with both a leading template and a TEMPLATE option:
tempfile ('arg_XXXX', TEMPLATE => 'opt_XXXX') Got arg_gIL3 tempdir ('arg_XXXX', TEMPLATE => 'opt_XXXX') Got arg_xPXg File::Temp->new ('arg_XXXX', TEMPLATE => 'opt_XXXX') Got /var/folders/5t/sy1gxkwj2l1gfd20s2g470200000gn/T/AYeB74PT0K File::Temp->newdir ('arg_XXXX', TEMPLATE => 'opt_XXXX') Got arg_GfwP
For everything except new()
, the TEMPLATE argument is ignored and the leading argument works just like in the documented API. But how about new()
? You see what’s happening don’t you? Here’s what it thinks you did:
File::Temp->new( arg_XXXX => 'TEMPLATE', opt_XXXX => undef );
Since none of those keys are known, it uses the default directory and template.
What about more wrong variations:
File::Temp->newdir ( TEMPLATE => 'opt_XXXX') Got /var/folders/5t/sy1gxkwj2l1gfd20s2g470200000gn/T/AkI6pFjyq_ File::Temp->tempfile('arg_XXXX' ) Got /var/folders/5t/sy1gxkwj2l1gfd20s2g470200000gn/T/3F2V8UPIbx File::Temp->tempdir ('arg_XXXX' ) Got /var/folders/5t/sy1gxkwj2l1gfd20s2g470200000gn/T/aSljGO6feU File::Temp->tempfile('arg_XXXX', TEMPLATE => 'opt_XXXX') Got /var/folders/5t/sy1gxkwj2l1gfd20s2g470200000gn/T/MGCo_TSXX5 File::Temp->tempdir ('arg_XXXX', TEMPLATE => 'opt_XXXX') Got /var/folders/5t/sy1gxkwj2l1gfd20s2g470200000gn/T/TEAXNoECbB
We get more weird behavior. The newdir
method doesn’t know about TEMPLATE. And calling functions as methods is like doing this:
tempfile( 'File::Temp' => 'arg_XXXX', TEMPLATE => 'opt_XXXX' );
Again, it can’t find the template and the default is used.
And finally, there’s this:
File::Temp->tempfile( TEMPLATE => 'opt_XXXX') Error in tempfile() using File::Temp: The template must end with at least 4 'X' characters File::Temp->tempdir ( TEMPLATE => 'opt_XXXX') Error in tempdir() using File::Temp: The template must end with at least 4 'X' characters
Why is that an error when the previous method calls weren’t? Because it looks like this:
tempfile( 'File::Temp', TEMPLATE => 'opt_XXXX' );
Since there are an odd number of arguments, it thinks it was given a (bad) leading template and some arguments.
If you’re ready to facepalm, go right ahead.
What about File::Temp 0.23 🔗︎
In 0.23, sanity (of a sort) returns. All the functions and methods now respect both ways of specifying a template.
tempfile ('arg_XXXX', TEMPLATE => 'opt_XXXX'); # fine File::Temp->newdir ( TEMPLATE => 'opt_XXXX'); # fine
If you specify both, the last one wins, just as if you gave multiple TEMPLATE arguments.
But there is a catch.
Calling the functions as methods is now an error. In 0.22, you could call functions as methods and File::Temp would (usually) just quietly give you a tempfile where you didn’t expect it. That was a bug and now it’s a fatal error.
Here’s the same test program under 0.2301:
tempfile ('arg_XXXX' ) Got arg_l2TB tempdir ('arg_XXXX' ) Got arg_5y15 File::Temp->new ( TEMPLATE => 'opt_XXXX') Got opt_sziU File::Temp->newdir ('arg_XXXX' ) Got arg_3imY tempfile ('arg_XXXX', TEMPLATE => 'opt_XXXX') Got opt_NTAn tempdir ('arg_XXXX', TEMPLATE => 'opt_XXXX') Got opt_TZzT File::Temp->new ('arg_XXXX', TEMPLATE => 'opt_XXXX') Got opt_CFPu File::Temp->newdir ('arg_XXXX', TEMPLATE => 'opt_XXXX') Got opt_ueeQ File::Temp->newdir ( TEMPLATE => 'opt_XXXX') Got opt_vkNh File::Temp->tempfile('arg_XXXX' ) 'tempfile' can't be called as a method at (eval 19) line 1. File::Temp->tempdir ('arg_XXXX' ) 'tempdir' can't be called as a method at (eval 20) line 1. File::Temp->tempfile('arg_XXXX', TEMPLATE => 'opt_XXXX') 'tempfile' can't be called as a method at (eval 21) line 1. File::Temp->tempdir ('arg_XXXX', TEMPLATE => 'opt_XXXX') 'tempdir' can't be called as a method at (eval 22) line 1. File::Temp->tempfile( TEMPLATE => 'opt_XXXX') 'tempfile' can't be called as a method at (eval 23) line 1. File::Temp->tempdir ( TEMPLATE => 'opt_XXXX') 'tempdir' can't be called as a method at (eval 24) line 1.
If you are calling functions as methods, your code will break. This is sensible because functions and method have very different scope implications.
- Functions are “global”: files and directories get cleaned up at the end of the program
- Methods are “lexical”: they return objects that clean up when the object is destroyed
If you are calling a function as a method, File::Temp has no way to know which way you want it and so it can’t DWIM. So BOOM! It dies.
Now go fix your code.