Version 43 of Creating Temporary Files

Updated 2022-04-16 17:01:18 by pooryorick

There are two preferred, modern ways to make temporary files:

  1. Tcl offered file tempfile for creating temporary files.
  2. Tcllib provides commands for tempdir and tempfile within the 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 Tcllib.

See Also

Extral, by Peter De Rijk
Provides commands for handling temporary files.
fileutil::tempfile and [fileutil::maketempir
Create temporary files and directories.
ycl dir autocreate
Creates a temporary directory. TIP #431 Discussion has some discussion and code for creating 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
    }
}