John Montague
John Montague

Reputation: 2010

Golang Generate Unique Filename With Extension

I want to generate unique filenames in Golang with extensions. Very much akin to ioutil.TempFile, which only takes prefixes.

This has been brought up many times in the forums https://groups.google.com/forum/#!topic/golang-nuts/PHgye3Hm2_0 and the Go goes seem pretty intent on not adding that functionality to TempFile.

So what's the suggested way to handle this? Should I just copy/paste the TempFile code and add in a suffix parameter?

Upvotes: 12

Views: 25451

Answers (4)

Zombo
Zombo

Reputation: 1

Starting with Go 1.16 (Feb 2021), you can use os.CreateTemp:

package main
import "os"

func main() {
   dirs := []string{"", "."}
   for _, dir := range dirs {
      f, err := os.CreateTemp(dir, "*.txt")
      if err != nil {
         panic(err)
      }
      defer f.Close()
      println(f.Name())
   }
}

Result:

C:\Windows\TEMP\163991747.txt
.\2499233563.txt

https://godocs.io/os#CreateTemp

Upvotes: 9

VonC
VonC

Reputation: 1324673

Update (2020: the original answer is from 2015)

As noted in Lax's answer, Go 1.11 (Apr. 2018) has changed TempFile prefix to a pattern.

See commit 191efbc from CL 105675 after issue 4896

Users of TempFile need to be able to supply the suffix, especially when using operating systems that give semantic meaning to the filename extension such as Windows.
Renaming the file to include an extension after the fact is insufficient as it could lead to race conditions.

If the string given to TempFile includes a "*", the random string replaces the "*".

For example "myname.*.bat" will result in a random filename such as "myname.123456.bat".

If no "*' is included the old behavior is retained, and the random digits are appended to the end.

If multiple "*" are included, the final one is replaced, thus permitting a pathological programmer to create filenames such as "foo*.123456.bat" but not "foo.123456.*.bat"


Original Answer (2015)

Should I just copy/paste the TempFile code and add in a suffix parameter?

That would be one way.
Other way is to make a quick -- crude -- implementation, as in this project:

// TempFileName generates a temporary filename for use in testing or whatever
func TempFileName(prefix, suffix string) string {
    randBytes := make([]byte, 16)
    rand.Read(randBytes)
    return filepath.Join(os.TempDir(), prefix+hex.EncodeToString(randBytes)+suffix)
}

As James Henstridge comments below, this is a crude function:

That function can return file names that already exist, for instance. Such an API should be creating the file by opening it with O_CREAT | O_EXCL to ensure that no one else creates the file between deciding on the name and creating the file.

That crude function above illustrates only the use of rand.Read() to generate a filename.

But the other checks are all in io/ioutil/tempfile.go.
3of3 suggests to use a function in math.rand instead of copying the random number generator in io/ioutil/tempfile.go.

Upvotes: 7

LaX
LaX

Reputation: 453

To anyone who would read this thread more recently, as of now (April 2020), ioutil.TempFile handles both prefix and suffix.

From its documentation:

TempFile creates a new temporary file in the directory dir, opens the file for reading and writing, and returns the resulting *os.File. The filename is generated by taking pattern and adding a random string to the end. If pattern includes a "*", the random string replaces the last "*".

As such, passing "prefix-*-suffix" as the pattern argument would yield the expected behaviour.

Upvotes: 2

Ztyx
Ztyx

Reputation: 14908

Have a look at https://github.com/tink-ab/tempfile. It's basically what the standard library supplies, but with support for both prefix and suffix.

Upvotes: 3

Related Questions