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

Hi Monks, I am writing a CGI application which basically creates a graph and displays it to the screen. My subroutine to do this (sub plot_graph) does this fine. However, I want the graph to be written to a different file each time the CGI is run - i have tried to do this by inserting  $$ in the graph filename but it stops working as soon as I do this. Can anyone suggest why this is happening and/or give some other suggestions. Cheers
plot_graph (); sub plot_graph { my ($temps, $numbers) = @_; # my @graph_data = (\@temps, \@numbers); my @graph_data = ( ["31768","25498", "21736", "17138", "9614", "6688", + "5016", "4180", "-2508", "-3762", "-5434", "-10450", "-12122", "-171 +38", "-20482", "-20900"], [84.44, 68.55, 56.43, 39.71, 26.57, 15.05, 7.11, 1.93, -9.61, -17.56, +-22.15, -34.69, -40.96, -48.91, -63.95, -71.04] ); my $graph = GD::Graph::lines ->new (600,600); $graph->set( x_label => 'Temp', y_label => 'Fluor', x_max_value =>100 ) or die $graph->error; $graph->set (dclrs=> [qw(green red blue)]); my $gb = $graph->plot(\@graph_data) or die $graph- +>error; open (IMG , '>out.png') or die $!; binmode IMG; print IMG $gb->png; close IMG; print qq(<img src=out.png>); }

Replies are listed 'Best First'.
Re: Automatically creating incremental file names
by TomDLux (Vicar) on May 27, 2003 at 16:12 UTC

    Perl Cookbook item 7.5: Creating Temporary Files.

    use IO::File; use POSIX qw(tmpnam); my $name; do{ $name = tmpnam() } until $fh = IO::File->new( $name, O_RDWR|O_CREAT|O_EXCL ); binmode $fh;

    Use $fh as the file handle, instead of IMG

    Don't forget to change the file name in the <IMG SRC= .. print statement. You'll need some way to delete old files every once in a while.

Re: Automatically creating incremental file names
by tcf22 (Priest) on May 27, 2003 at 15:59 UTC
    open (IMG , '>out.png') or die $!;
    If you were trying
    open (IMG , '>$$.png') or die $!;
    You would have to use double quotes so $$ is interpolated. You didn't show the example of you using the pid for the filename so I could off on your problem.

    As for creating a unique filename, I prefer the quick and dirty way
    my $filename = $$ . time() . '.png';
Re: Automatically creating incremental file names
by hardburn (Abbot) on May 27, 2003 at 15:48 UTC

    Don't use the PID for things like this. It's not as random as you think it is (OpenBSD is the only exception that I know of).

    You could try loading all your data into a scalar, and then running an SHA1 hash on that data. The base64 value of the hash will be your filename. Just in case the impossible happens, check for the existance of that file and append some data to it each time through the loop, such as:

    $filename .= '0' while(-e $filename);

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    Note: All code is untested, unless otherwise stated

      Actually, if the data for a current run produces the same SHA1 hash string (or the same MD5 signature, if one prefers) as some other file that already exists, wouldn't that mean that the existing file already contains exactly the same data as the current run? In that case, you have a built-in means to decide when a new file doesn't need to be written to disk (because the data in question are already there).

        . . . wouldn't that mean that the existing file already contains exactly the same data as the current run?

        For SHA1, there is a 1 in 2**160 chance that it isn't the same data, but produces the same hash. Actually, because of the Birthday Paradox (external link), it's more like 1 in 2**80.

        This chance is so tiny that you could ignore it and probably get away with it, even if your program was running until the sun burned out. Even so, I like to be certain if at all possible, which is why I had that while loop in my post above.

        ----
        I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
        -- Schemer

        Note: All code is untested, unless otherwise stated

Re: Automatically creating incremental file names
by BrowserUk (Patriarch) on May 27, 2003 at 16:08 UTC

    My suggestion would be to use the time function from Time::HiRes.

    use Time::HiRes qw[time]; printf "%.6f\n", time for 1..10; 1054051486.970288 1054051486.980302 1054051486.990316 1054051487.000331 1054051487.010345 1054051487.010345 1054051487.020360 1054051487.030374 1054051487.040388 1054051487.050403
    That should give you a unique time (to the microsecond if your platform supports it), and should be okay until something like 2038?

    Update: The timestamp is only a starting point.
    If you are creating your temp files on shared media (suggestion:Don't!), or if you are running on a multi-processor machine, then you would need to combine the timestamps with additional information to aceive uniqueness. Processor ID in the latter case would work.
    In the former case, which I would advise against, but if you have access to the hardcoded MAC addresses, this is a good starting point.

    A bonus is that the resultant filenames are easily compared so cleaning up the directory periodically should be trivial.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller

      Note you output two identical names: 1054051487.010345, not a good trait for making unique filenames. You're at the mercy of the platform's performance if you depend on timestamps alone. Combining the value with something else that's dynamic, such as a PID and/or a simple $counter++ is preferable, but still weak.

      To really pick unique symbols, you should look for GUID or UUID generators, that's what they're for. A thousand machines running side by side won't even be able to pick the same symbol twice. Data::UUID seems useful.

      If you want unique temporary filenames, please look for modules that do this. File::Temp for example. These often handle file cleanup too.

      Simple timestamps, counters, and -e checks may introduce new race conditions only discovered when you try to scale up to a few hundred or thousand independent concurrent tasks. Don't reinvent the wheel, just to rediscover the limitations of a naive approach.

      --
      [ e d @ h a l l e y . c c ]

        Piffle!

        Addendum: The output shown, was not a list of names, but a list of times.

        Have you looked in File::Temp?

        Have you tried using it? Have you seen how slow and cumbersome it is?

        Have you seen the race conditions it creates?

        Have you seen the list of caveats and exclusions when used on systems other that *nix? Ie. VMS, Win32.

        ANY mechanism for determining unique file names that works by guessing a possible candidate, and then looking to see if it already exists, is doomed to failure in multi-processor and/or shared media environments.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
      That should give you a unique time (to the microsecond if you platform supports it), and should be okay until something like 2038?

      That doesn't give any garantee for uniqueness when there is more than one processor in the machine. And those will be commonplace long before 2038. If you want uniqueness, don't depend on only a clock.

      Abigail

        Agreed. In a multi-processor environments, additional steps must be taken.

        Likewise, if storing temporary file on shared media.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
Re: Automatically creating incremental file names
by dash2 (Hermit) on May 27, 2003 at 18:35 UTC
    I don't know exactly what you are trying to do, but one lateral solution would be: don't print the graph to a file, instead just print it straight to the browser, preceded by an appropriate HTTP header. Then call your perl script directly from within your image tags.

    andramoiennepemousapolutropon

Re: Automatically creating incremental file names
by nimdokk (Vicar) on May 27, 2003 at 16:43 UTC
    Depending on what you are trying to do, you could do something with localtime to keep the names unique. We do something like this:

    $file=sprintf("%03d%02d%02%02d",localtime->yday(),localtime->hour(),lo +caltime->min(),localtime->sec());
    That gives us a unique enough number for our purposes.

    "Ex libris un peut de tout"

Re: Automatically creating incremental file names
by adamk (Chaplain) on May 28, 2003 at 11:45 UTC

    OR

    Just let someone else handle the uniqueness issue.

    If you are on Apache, just use the $ENV{UNIQUE_ID} value to get your unique string, and go from there...

    The Apache module that does it is http://httpd.apache.org/docs/mod/mod_unique_id.html and it quite probably either already installed on your apache, although it might not be enabled. You just remove the comments, and restart.

Re: Automatically creating incremental file names
by richardX (Pilgrim) on May 28, 2003 at 02:45 UTC
    Here is a snippet from a program that I use daily that checks for a duplicate file name in the out bucket and renames the in bucket filename if it already exists.

    #check for dup name if (-e "$fn_out/$fname") { print "Dup:: $fn_out/$fname\n"; my ($name, $ext) = ($fname =~ /^(.+?)(\.[^.]+)?$/); $ext = "" unless $ext; my $i = 0; while (-e "$fn_out/$fname_new") { ++ $i; $fname_new = sprintf "$name-%02d$ext", $i; } my $uni = $fname_new; print "New:: $fn_out/$uni\n\n"; rename ("$fn_in/$fname", "$fn_out/$uni"); }else{ rename ("$fn_in/$fname", "$fn_out/$fname"); print "Dest:: $fn_out/$fname\n\n"; }

    $fn_in is the directory for the in bucket of file names. $fn_out is the directory for the out bucket of file names

    The critical line is a self incrementing line until it finds a file name that does not exist in the out bucket.

    $fname_new = sprintf "$name-%02d$ext", $i;

    The format of the file name will be FILENAME-NN.EXT where NN is an incrementing number. You can easily customize this to your likes.

    I like using this solution because it works on all platorms and does not require any overhead or modules.

    Richard

    There are three types of people in this world, those that can count and those that cannot. Anon

      Do you use this approach in a CGI script that is supposed to create files and not over-write/re-use existing file names (as the OP wants to do)?

      If so, the first "customization" that would be needed is something like a separate semaphore file, to be locked before doing the "if (-e ...)" test, and released after leaving whichever block is executed as a result of that test. Otherwise, two competing processes on the same web server could still collide on the same file name.

      (Another customization I'd want is to indent the code properly. But personally, I think the initial reply in this thread, suggesting a SHA1 signature as the file name, has the most merit.)