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.