Version 22 of Creating Temporary Files

Updated 2011-05-08 03:28:16 by zhangweiwu

There are two ways to make temperory files:

  1. TCL offered file tempfile for creating temperory files.
  2. Tcllib provides commands for tempdir and tempfile:

http://tcllib.sourceforge.net/doc/fileutil.html

However this page is delicated to ways to create temperory files by adding a bit TCL code, for people who targets distributions lower than tcl 8.6 (not having file tempfile) and do not wish to depend on tcllib.


Best is to figure out the temp directory separately. Then you can create multiple files in it.

 switch $tcl_platform(platform) {
    unix {
        set tmpdir /tmp   # or even $::env(TMPDIR), at times.
    } macintosh {
        set tmpdir $::env(TRASH_FOLDER)  ;# a better place?
    } default {
        set tmpdir [pwd]
        catch {set tmpdir $::env(TMP)}
        catch {set tmpdir $::env(TEMP)}
    }
 }

Ingemar Hansson - Added double colons above to "env" since it isn't global declared

If you just want one temp file:

 set filename [file join $tmpdir [pid]]

If you want multiple tmpfiles, consider appending things like the application name, counter, time stamp, etc. For example:

 set file [file join $tmpdir $appname.[pid].[incr ::globalCounter]]

DKF - Or create in a subdirectory/subfolder

 if {![file exists [file join $tmpdir [pid]]]} {
    file mkdir [file join $tmpdir [pid]]
 }
 set file [file join $tmpdir [pid] [incr ::globalCounter]]

Michael Schlenker - Be aware of potential race conditions while opening temp files. The linux secure programming howto has some nice examples for the linux platform: http://en.tldp.org/HOWTO/Secure-Programs-HOWTO/avoid-race.html

To be sure to get a new tempfile (and not some evil symlink) try something like:

 set access [list RDWR CREAT EXCL TRUNC]
 set perm 0600
 if {[catch {open $file $access $perm} fid ]} {
     # something went wrong 
     error "Could not open tempfile."
 } 
 # ok everything went well

I had some luck with the following procedure on a UNIX box. Maybe, somebody can check if it works for windows.

 proc tempfile {prefix suffix} {
     set chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
     set nrand_chars 10
     set maxtries 10
     set access [list RDWR CREAT EXCL TRUNC]
     set permission 0600
     set channel ""
     set checked_dir_writable 0
     set mypid [pid]
     for {set i 0} {$i < $maxtries} {incr i} {
  set newname $prefix
  for {set j 0} {$j < $nrand_chars} {incr j} {
      append newname [string index $chars \
       [expr ([clock clicks] ^ $mypid) % 62]]
  }
  append newname $suffix
  if {[file exists $newname]} {
      after 1
  } else {
      if {[catch {open $newname $access $permission} channel]} {
   if {!$checked_dir_writable} {
       set dirname [file dirname $newname]
       if {![file writable $dirname]} {
    error "Directory $dirname is not writable"
       }
       set checked_dir_writable 1
   }
      } else {
   # Success
   return [list $newname $channel]
      }
  }
     }
     if {[string compare $channel ""]} {
  error "Failed to open a temporary file: $chanel"
     } else {
  error "Failed to find an unused temporary file name"
     }
 }

Igor Volobouev


Stu - My version of mkstemp(3)

 proc randazAZchar {} {
     return [format %c [expr {int(rand() * 26) + [expr {int(rand() * 10) > 5 ? 97 : 65}]}]]
 }
 proc randazAZstring {length} {
     set s {}
     for {set i $length} {$i > 0} {incr i -1} {append s [randazAZchar]}
     return $s
 }
 proc makeTempFile {template} {
     if {[set xi [string first "X" $template]] == -1} {
         set n $template
         set p {}
         set rl 0
     } else {
         set n [string range $template 0 [expr {$xi - 1}]]
         set p [pid]
         set rl [expr {[string length $template] - $xi - [string length $p]}]
         if {$rl < 0} {
             set p [string range $p 0 end-[expr {$rl * -1}]]
             set rl 0
         }
     }
     for {set i [expr {int(pow(26, ($rl <= 6 ? $rl : 6)))}]} {$i > 0} {incr i -1} {
         set fn $n[randazAZstring $rl]$p
         if {![catch {open $fn {CREAT EXCL RDWR} 0600} fd]} {
             file delete $fn
             return $fd
         }
         if {[lindex $::errorCode 1] ne "EEXIST" || [lindex $::errorCode 0] ne "POSIX"} {
             break
         }
     }
     return -code error -errorcode $::errorCode $fd
 }

wld

Have a look at GUID and UUID for generating unique filenames for temp-files.