os/temp
Making a temporary file or directory under a unique name.
import os.temp
For data that is only needed while the program runs, make a temporary file or directory: an unpacked archive, a file being built before it is published, the output of a tool. File and Directory create an empty one in the system temporary folder under a random name (salam- and 16 hex digits from crypto), so several copies of the program running at once do not collide, and return its path. Removing it is up to you: nothing is cleaned up when the program exits.
Read and write the files with fs.file, and build paths inside a temporary directory with fs.path. For replacing a real file safely, fs.file.AtomicWrite is often the better tool.
What people get wrong:
- Not checking
Directory. It returns empty text when it fails, and a laterpath.Join("", name)then writes into the current folder. - Trusting
Fileblindly. It always returns a path, even when the file could not be created, and it creates the file first and narrows its permissions right after, so for a moment it has the default permissions. For secrets, make aDirectory(only you can enter it) and create the files inside it. - Moving a finished file out with a rename. The temporary folder is often on another disk, where a rename fails; copy it instead.
A scratch directory for one job
The usual pattern is three lines: make a directory with Directory, put defer temp.RemoveDir(work) right under it, and build every path inside it with path.Join. The deferred call runs on every way out of the function, an early ret included, so the directory is gone however the job ends. Directory returns an empty string when it cannot make the directory, so check for that before you use it.
import fs.dir
import fs.file
import fs.path
import os.temp
func main:
work := temp.Directory()
if work == "":
println "could not make a work directory"
ret 1
end
defer temp.RemoveDir(work)
file.WriteText(path.Join(work, "part-1.txt"), "Salam ")
file.WriteText(path.Join(work, "part-2.txt"), "world\n")
mut joined := ""
each name in ["part-1.txt", "part-2.txt"]:
joined = joined + file.ReadText(path.Join(work, name))
end
print joined
println dir.ListFiles(work).len(), "parts,", dir.Size(work), "bytes"
ret 0
endBuild a file in steps, then publish it
When a file is written in several steps, write it to a temporary file first and copy it to its real name only when it is complete. Whoever reads people.csv then never sees half of it. Use file.Copy here, not file.Move: the temporary directory is often on another disk (a tmpfs in memory on many Linux systems), and a rename cannot cross disks.
import fs.file
import os.temp
func main:
staging := temp.File()
file.Append(staging, "id,name\n")
file.Append(staging, "1,Sara\n")
file.Append(staging, "2,Reza\n")
rows := file.ReadLines(staging)
println "staged", rows.len(), "lines"
if file.Copy(staging, "people.csv") == 0:
println "published people.csv,", file.Size("people.csv"), "bytes"
end
temp.Remove(staging)
println "staging left behind:", file.Exists(staging)
print file.ReadText("people.csv")
file.Delete("people.csv")
endWhen the whole content is ready in one string, fs.file.AtomicWrite is simpler: it writes the temporary copy next to the target, on the same disk, and renames it in one step.
Who can read a temporary file
The system temporary directory is shared by every account on the machine. That is why File makes its file readable and writable by you alone (600) and Directory makes its directory open to you alone (700). A token or a downloaded document you put there stays out of reach of other users. Leave it like that, and widen the permissions with fs.Chmod only when another account really has to read the file.
import fs
import os.temp
func main:
secret := temp.File()
work := temp.Directory()
println "file:", fs.Permissions(secret)
println "directory:", fs.Permissions(work)
temp.Remove(secret)
temp.RemoveDir(work)
endErrors, and where the files go
Remove and RemoveDir return 0 on success and -1 on failure, and they record an IOError in os.error. The usual failure is removing the same file twice. RemoveDir on a directory that is already gone counts as success.
import os.error
import os.temp
func main:
p := temp.File()
println "first remove:", temp.Remove(p)
error.Clear()
println "second remove:", temp.Remove(p)
if error.HasError():
println "message:", error.LastMessage()
println "is an I/O error:", error.LastCode() == error.IOError
end
endThe files go into the directory that os.TempDir names: the TEMP, TMP or TMPDIR environment variable, checked in that order, and /tmp when none is set. Nothing removes them when the program exits, and a program that crashes leaves them behind, so a long running service should give each job its own directory and remove it when the job is done. The names are random (salam- and hex digits), so never compare them or show them in output that someone reads later.
Functions
File
func File(): strsalam- plus 16 random hex digits and .tmp in the system temporary directory, sets its permissions to 600 and returns its path. It returns the path even when creation failed.import os
import os.temp
func main:
p := temp.File()
println os.Exists(p)
temp.Remove(p)
println os.Exists(p)
d := temp.Directory()
println os.IsDir(d)
temp.RemoveDir(d)
println os.Exists(d)
endDirectory
func Directory(): str700 and returns its path, or empty text (with an os.error record) when it cannot.The example for this item is shown with the ones above: os-temp-File.salam
Remove
func Remove(p: str): intos.error record on failure.The example for this item is shown with the ones above: os-temp-File.salam
RemoveDir
func RemoveDir(p: str): intos.RemoveAll; 0 on success or when it is already gone, -1 on failure. Like os.RemoveAll, it follows symbolic links to folders.The example for this item is shown with the ones above: os-temp-File.salam