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

I got this sub to generate a random filename for my app that renames stuff.
sub random_filename { my($length_limit, $extension); # examples: $length_limit = +255, $extension = '.jpg' my($current_length, $random_filename); $length_limit = shift(); $extension = shift(); for ($current_length = 1; $current_length >= $length_limit; $curre +nt_length++) { $random_filename .= chr(int(rand(26) + 97)); # range from l +owercase a to lowercase z last() unless (int(rand($length_limit))); # 1/$length_l +imit chance of quitting the loop each run through } $random_filename .= $extension; return($random_filename); }
But it always returns a string with just $extension, and I have not a clue why. Any aid?

Replies are listed 'Best First'.
Re: generating random filenames
by atl (Pilgrim) on Jul 10, 2001 at 19:33 UTC
    You probably meant
    ... $current_length <= $length_limit; ...
    (less or equal instead of greater or equal).

    Greetings

    Andreas

    Addendum: a walkthrough in the debugger or a print statement inside the loop helps finding such problems. :-)

    Have fun ...

      Isn't the middle argument the condition that's true to terminate the loop? ...I guess not, cuz it seems to work now.
        Nope, just the opposite. As long as the the middle part evaluates true the loop continous.

        <irony> Never mind, at least this gave me a question I was able to answer. </irony>

        Have(ing) fun ...

        Andreas

Re: generating random filenames
by Albannach (Monsignor) on Jul 10, 2001 at 19:54 UTC
    While others have pointed out your error, and shown you the "Perlish" loop syntax that helps you avoid this type of error, you should know that the filename being random in no way ensures that a file of that name does not already exist, especially in this case if someone calls your subroutine with a small value of $length_limit. Better would be to incorporate a timestamp of some kind in addition to the random characters, but you still need to check that the file does not exist before you start to use the name.

    For some other ideas on this topic, please have a look at "unique" filename?, which I located in a few seconds using SuperSearch - try it!

    --
    I'd like to be able to assign to an luser

      right-o, albannach!

      my personal favorite is

      use POSIX; use IO::Handle; # create temp file my $TMPFH = new IO::Handle; do { $tmpfile = $mytmpdir . tmpnam() } until sysopen(TMPFH, $tmpfile, O_RDWR|O_CREAT|O_EXCL); # do something, like write to the tempfile foreach(@list) { print TMPFH $_,$n } # ... close TMPFH; # do something, like read in the tempfile... # when you're done, delete it unlink($tmpfile) or warn("WARN: cannot delete $tmpfile! $!");

      ~Particle

      I like to use a UUID for the name. Some operating systems have a function for that (e.g. GetTempFileName in Win32).
Re: generating random filenames
by PrakashK (Pilgrim) on Jul 10, 2001 at 19:36 UTC
    for ($current_length = 1; $current_length >= $length_limit; $curre +nt_length++) { .... }
    This loop never executes, because of incorrect checking of $current_length. Change the >= to <=:
    for ($current_length = 1; $current_length <= $length_limit; $curre +nt_length++) { .... }
    Or, better yet:
    for $current_length (1 .. $length_limit) { .... }
    /prakash
(crazyinsomniac:)Re: generating random filenames
by crazyinsomniac (Prior) on Jul 11, 2001 at 01:36 UTC
    Hi.

    While developing an upcoming perl monks utility release (xml_pimp) I went through a gamut ideas, in the midst of which, i thought about generating a temporary file.

    I was messing with bunch of new things at the moment, some of which include the printf functions, regexes, inline pod ... ;)

    None the less, here is what came out of it. Read it, test it, use it if you like.

    ++++What I went through to figure out how the generate the filename comes first, and my test script (that actually created and deleted files), comes after that .

    #!/usr/bin/perl -w use strict; =head1 I MISUNDERSTOOD MY INPUT AND FUNCTION Apparently sprintf does not provide maximum length for number types, but only for strings. So in effect i was getting ints wih 8 or 9 digits before the . and the min length was 8, but you can't specify max length. Live and learn, and then make an ass out of yourself (almost) ;-^)~ I ended up making sure my input was an integer, and then treating it as a string with max and min length specified. Basically i feed it to sprintf twice =cut =head1 SPRINTF NOT PADDING CORRECTLY (original question) print sprintf(".crazy.%010u.temp",rand(time)),"\n"; print sprintf(".crazy.%08.8s.temp",rand(time)),"\n"; Its' fine if i use string, but it's supposed to be minimum length for + int but it doesn't pad with d (signed int), or with u (unsigned int). unless it's the length is 9 or greater. Is this flaw, or what? If i use 8, some numbers don't get padded. If i use 9 or greater, they do. I don't think this has to do with my specific version of perl, which is ActiveState 5.6(build 623) btw. Why, cause perldoc:pelrfunc says: Perl does its own sprintf formatting--it emulates the C function spri +ntf, but it doesn't use it (except for floating-point numbers, and even th +en only the standard modifiers are allowed). As a result, any non-standa +rd extensions in your local sprintf are not available from Perl. And since i'm not trying to represent a floating point number (i want + int), it should make no difference(not that it would anyway). Also, I do not wish to use %08s (for string), because sometimes a . s +neaks in, which is because rand(time) outputs a positive float that looks l +ike BTW - rand(time) yields results like: 598318636.889648 52034455.75 52445775.5732422 However, it hardly yields a value less than 8 digits digits bef +ore the decimal, but that's besides the point. BTW - srand is called automatically in my version of perl, so i don't call it explicitly cause it makes my results duplicates 1 +/3 of the time. =cut for(0..99) { print ' rand( ', rand(time),"\n"; } =head1 WHAT I TRIED =pod Here come the un-signed integer examples. sprintf(".crazy.%08u.temp" ,rand(time)) # min length 8, padd w/0 sprintf(".crazy.%08.8u.temp" ,rand(time)) # min length 8 or 8, padd w +/0 sprintf(".crazy.%010u.temp" ,rand(time)) # min length 10, padd w/0 sprintf(".crazy.%0.8u.temp" ,rand(time)) # not actually sure It's either padd with 0 w/min length of 8, or BTW - rand(time) yields results like: 598318636.889648 52445775.5732422 =head1 MY SOLUTION sprintf(".crazy.%08.8s.temp", sprintf("%u",rand(time)) ); Feed sprintf an unsigned int, and then feed that unsigned int to a sprintf with 0 for padding and a string of max length 8 and min length 8 (you gotta specify both to get a fixed width) or sprintf(".crazy.%08.8s.temp", int(rand(time)) ); It's a little bit faster, cause it uses int, but it's the same shit. =cut print &_titled_hr(' sprintf(".crazy.%010.10s.temp", sprintf("%u",rand( +time))) '); for(0..99) { print ' %010.10s(', sprintf(".crazy.%010.10s.temp", sprintf("%u",rand(time))), "\n"; } print &_titled_hr(' sprintf(".crazy.%010.10s.temp",rand(time)) '); for(0..99) { print ' %010.10s(', sprintf(".crazy.%010.10s.temp", int( rand(time) ) ), "\n"; } print &_titled_hr(' sprintf(".crazy.%08u.temp",rand(time)) '); for(0..99) { print ' %08u(', sprintf(".crazy.%08u.temp",rand(time)),"\n"; } print &_titled_hr(' sprintf(".crazy.%0.8u.temp",rand(time)) '); for(0..99) { print ' %0.8u(', sprintf(".crazy.%0.8u.temp",rand(time)),"\n"; } print &_titled_hr(' sprintf(".crazy.%08.8u.temp",rand(time)) '); for(0..99) { print ' %08.8u(', sprintf(".crazy.%08.8u.temp",rand(time)),"\n"; } print &_titled_hr(' sprintf(".crazy.%010u.temp",rand(time)) '); for(0..99) { print ' %010u(', sprintf(".crazy.%010u.temp",rand(time)),"\n"; } =pod Here come the signed integer examples. sprintf(".crazy.%08d.temp" , rand(time))) sprintf(".crazy.%0.8d.temp" , rand(time))) sprintf(".crazy.%08.8d.temp" , rand(time))) sprintf(".crazy.%010d.temp" , rand(time))) BTW - rand(time) always yields positive floats. =cut print &_titled_hr(' sprintf(".crazy.%08d.temp",rand(time)) '); for(0..99) { print ' %08d(', sprintf(".crazy.%08d.temp",rand(time)), "\n"; } print &_titled_hr(' sprintf(".crazy.%0.8d.temp",rand(time)) '); for(0..99) { print ' %0.8d(', sprintf(".crazy.%0.8d.temp",rand(time)), "\n"; } print &_titled_hr(' sprintf(".crazy.%08.8d.temp",rand(time)) '); for(0..99) { print ' %08.8d(', sprintf(".crazy.%08.8d.temp",rand(time)), "\n" +; } print &_titled_hr(' sprintf(".crazy.%010d.temp",rand(time)) '); for(0..99) { print ' %010d(', sprintf(".crazy.%010d.temp",rand(time)), "\n"; } exit; sub _titled_hr { my $string= join('', @_); my $oy = int (80 -(length $string) )/ 2; $oy -=2; return "\n ","-" x $oy, $string, "-" x $oy," \n"; }; ###################################################################### +######## ## END OF SCRIPT __END__

    Here is the test code, for what i was attempting (very messy)

    #!/usr/bin/perl -w use strict; use Compress::Zlib; my $userfile = '.crazyinsomniac.nodes.hashoaray.dat'; my $tempout = sprintf(".crazy.%010.10s.temp", int(rand(time)) ); for(0..10) { &testshit; &cleanup_file; } sub testshit { &compressor(); for(0..100) { $tempout = sprintf(".crazy.%010.10s.temp", int(rand(time)) ); &decompressor(); } } sub cleanup_file { unlink $tempout if -e $tempout; opendir(DIN,'.') or die "can't opendir . ($!)\n"; my @files = grep {/^.crazy.(\d){10}.temp$/} readdir(DIN); print "trying to delete(",scalar(@files),"):\n", join "\n", @files +,"\n"; print 'successfully deleted ',(unlink @files)," files \n"; } sub decompressor { my $x = inflateInit() or die "Cannot create a inflation stream\n"; my ($output, $status,$input); open(FIN , "<".$userfile.'.gz') or die "can't open $userfile($!)\n +"; open(FOUT, ">".$tempout) or die "can't open $tempout.gz($!)\n"; binmode FIN; binmode FOUT; while (read(FIN, $input, 4096)) { ($output, $status) = $x->inflate(\$input) ; if( ( $status == Z_OK ) or ( $status == Z_STREAM_END ) ) { print FOUT $output; } last if $status != Z_OK ; } close(FIN); close(FOUT); #unlink $tempout; # you will be deleted # since we don't wanna die before deleting this die "inflation failed\n" unless $status == Z_STREAM_END ; } sub compressor { open(FIN , "<".$userfile) or die "can't open $userfile($!)\n"; open(FOUT, ">".$userfile.'.gz') or die "can't open $userfile.gz($ +!)\n"; binmode FIN; binmode FOUT; my $deflation_stream = deflateInit() or die "Cannot create a defla +tion stream\n"; while (read(FIN, $_, 4096)) { my ($output, $status) = $deflation_stream->deflate($_); $status == Z_OK or die "deflation failed\n"; print FOUT $output ; } my ($output, $status) = $deflation_stream->flush(); $status == Z_OK or die "deflation failed\n"; print FOUT $output ; } sub sprinter { for(0..1000) { print sprintf(".crazyinsomniac.%010u.xml_pimp.tpl",rand(time)) +,"\n"; # print sprintf(".crazyinsomniac.%08.8s.xml_pimp.tpl",rand(tim +e)),"\n"; # its' fine if i use string, but it's supposed to be minimum l +ength for int # but it doesn't pad with d (signed int), but it does with u. # unless it's 9 or > (cause input is always 8 or less) # is this flaw, or what? # this is really fucked up. If i use 8, some strings don't ge +t padded # if i use 9 or greater, they do } }

     
    ___crazyinsomniac_______________________________________
    Disclaimer: Don't blame. It came from inside the void

    perl -e "$q=$_;map({chr unpack qq;H*;,$_}split(q;;,q*H*));print;$q/$q;"

Re: generating random filenames
by particle (Vicar) on Jul 10, 2001 at 19:37 UTC
    your for loop is wrong.

    >= should be <=

    ~Particle

Re: generating random filenames
by tachyon (Chancellor) on Jul 10, 2001 at 22:18 UTC

    As has been pointed out there are better ways of generating a random/temporary file name that is both unique and guaranteed to stay that way as you have an exclusive lock on the file.

    It is good practice when generating random numbers to seed the random number generator first using srand. This works pretty well and carries the Camel seal of approval.

    srand(time()^($$+($$<<15))); # now do something really random
    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

      i was under the impression that this wasn't necessary anymore, and looking at srand,
      In fact, it's usually not necessary to call srand() at all, because if + it is not called explicitly, it is called implicitly at the first use of the + rand() operator. However, this was not the case in version of Perl before 5.0 +04, so if your script will run under older Perl versions, it should call sran +d()

      ~Particle

        While I could hide behind a "you need to do this for older versions of perl" the truth is I did not know you can now drop it :-) Thanks for the info. Getting slack on reading those man pages. I'll still probably use it for a while yet though just to make sure my random stuff is random across the board. 5.04 was a while ago now but some systems still use 4!

        cheers

        tachyon

        s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print