Creating Temporary Files

Difference between version 42 and 45 - Previous - Next
There are two preferred, modern ways to make temporary files:

   1. Tcl offered [file tempfile] for creating temporary files.
   1. [Tcllib] provides commands for tempdir and tempfile within the http://core.tcl.tk/tcllib/doc/trunk/embedded/www/tcllib/files/modules/fileutil/fileutil.html%|%fileutil%|% package.
This page is dedicated to ways to create temporary files by adding a bit of Tcl code, for people who target distributions older than Tcl 8.6 (not having [file tempfile]) and do not wish to depend on tTcllib.



** See Also **

   [Extral], by Peter De Rijk:   Provides commands for handling temporary files.

   [fileutil]::tempfile and [fileutil::maketempir:   Create temporary files and directories.
   [ycl%|%ycl dir autocreate]:   Creates a temporary directory.  [TIP #431 Discussion] has some discussion and code for createing temporary directories.



** Description **


The temp directory is put in to different environment variables on different systems. Examples are:

   Mac OS X:   TMPDIR (tested on 10.8.5)
   Windows 2000:   TMP and TEMP (tested on Windows 2000 SP3)
   CygWin:   TMP and TEMP (tested on Cygwin 1.7.9)

A scriptlet to do this:

======
set tmpdir [pwd]
if {[file exists /tmp]} {set tmpdir /tmp}
catch {set tmpdir $::env(TRASH_FOLDER)} ;# very old Macintosh. Mac OS X doesn't have this.
catch {set tmpdir $::env(TMP)}
catch {set tmpdir $::env(TEMP)}
======

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
    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 {int(rand() * 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 {$channl eq {}} {
        error [list {Failed to open a temporary file} $channel]
    } else {
        error [list {Failed to find an unused temporary file name}]
    }
}
======

Igor Volobouev

[AMG]: Here's a shorter version of the same:

======
proc tempfile {{filenameVar {}}} {
    set chars abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
    if {$filenameVar ne {}} {
        upvar 1 $filenameVar filename
    }
    for {set i 0} {$i < 10} {incr i} {
        set filename /tmp/tcl_
        for {set j 0} {$j < 10} {incr j} {
            append filename [string index $chars [expr {int(rand() * 62)}]]
        }
        if {![catch {open $filename {RDWR CREAT EXCL} 0600} channel]} {
            return $channel
        }
    }
    error {failed to find an unused temporary file name}
}
======

[AMG]: And here's a variation which creates temporary directories:

======
proc temporaryDirectory {} {
    set chars abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
    for {set i 0} {$i < 10} {incr i} {
        set path /tmp/tcl_
        for {set j 0} {$j < 10} {incr j} {
            append path [string index $chars [expr {int(rand() * 62)}]]
        }
        if {![file exists $path]} {
            file mkdir $path
            file attributes $path -permissions 0700
            return $path
        }
    }
    error {failed to find an unused temporary directory name}
}
======

However, it is racy because Tcl does not provide a way to atomically check for a directory's existence, create it if it doesn't exist, and give it specified permissions.

----
[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.



** Automatic disposal **


[dbohdan] 2015-04-26: I wrote the following procedure to manage temporary files in [tcltest] tests for [Sqawk]. It creates and opens temporary files, runs the code it was given and then automatically closes the files and deletes them. The design principle is the same as in [withOpenFile] and [with-path]. For Tcl 8.5 compatibility the procedure uses `::[fileutil]::tempfile` to generate filenames.

======
# Create and open temporary files (read/write), run a script then close and
# delete the files. $args is a list of the format {fnVarName1 chVarName1
# fnVarName2 chVarName2 ... script}.
proc with-temp-files args {
    set files {}
    set channels {}

    set script [lindex $args end]
    foreach {fnVarName chVarName} [lrange $args 0 end-1] {
        set filename [::fileutil::tempfile]
        uplevel 1 [list set $fnVarName $filename]
        lappend files $filename
        if {$chVarName ne {}} {
            set channel [open $filename w+]
            uplevel 1 [list set $chVarName $channel]
            lappend channels $channel
        }
    }
    uplevel 1 $script

    foreach channel $channels {        catch { close $channel }
    }
    foreach filename $files {
        file delete $filename
    }
}
======



<<categories>> Security | Tutorial | File