37

When using Tempfile Ruby is creating a file with a thread-safe and inter-process-safe name. I only need a file name in that way.

I was wondering if there is a more straight forward approach way than:

t = Tempfile.new(['fleischwurst', '.png'])
temp_path = t.path
t.close
t.unlink
iltempo
  • 15,718
  • 8
  • 61
  • 72

4 Answers4

62

Dir::Tmpname.create

You could use Dir::Tmpname.create. It figures out what temporary directory to use (unless you pass it a directory). It's a little ugly to use given that it expects a block:

require 'tmpdir'
# => true
Dir::Tmpname.create(['prefix-', '.ext']) {}
# => "/tmp/prefix-20190827-1-87n9iu.ext"
Dir::Tmpname.create(['prefix-', '.ext'], '/my/custom/directory') {}
# => "/my/custom/directory/prefix-20190827-1-11x2u0h.ext"

The block is there for code to test if the file exists and raise an Errno::EEXIST so that a new name can be generated with incrementing value appended on the end.

The Rails Solution

The solution implemented by Ruby on Rails is short and similar to the solution originally implemented in Ruby:

require 'tmpdir'
# => true
File.join(Dir.tmpdir, "YOUR_PREFIX-#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}-YOUR_SUFFIX")
=> "/tmp/YOUR_PREFIX-20190827-1-wyouwg-YOUR_SUFFIX"
File.join(Dir.tmpdir, "YOUR_PREFIX-#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}-YOUR_SUFFIX")
=> "/tmp/YOUR_PREFIX-20190827-1-140far-YOUR_SUFFIX"

Dir::Tmpname.make_tmpname (Ruby 2.5.0 and earlier)

Dir::Tmpname.make_tmpname was removed in Ruby 2.5.0. Prior to Ruby 2.4.4 it could accept a directory path as a prefix, but as of Ruby 2.4.4, directory separators are removed.

Digging in tempfile.rb you'll notice that Tempfile includes Dir::Tmpname. Inside you'll find make_tmpname which does what you ask for.

require 'tmpdir'
# => true
File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname("prefix-", nil))
# => "/tmp/prefix-20190827-1-dfhvld"
File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname(["prefix-", ".ext"], nil))
# => "/tmp/prefix-20190827-1-19zjck1.ext"
File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname(["prefix-", ".ext"], "suffix"))
# => "/tmp/prefix-20190827-1-f5ipo7-suffix.ext"
Brandon
  • 16,382
  • 12
  • 55
  • 88
Jan
  • 11,636
  • 38
  • 47
  • Thanks, that's it. Can be used with an array argument as well to preserve the file name extension: `Dir::Tmpname.make_tmpname(['a', '.png'], nil)` – iltempo Dec 09 '12 at 14:35
  • @iltempo, you're welcome. I've added your example to the answer. – Jan Dec 09 '12 at 14:38
  • 3
    This is great but nobody has mentioned that you need to `require 'tmpdir'` for this to work. – KingBob Aug 10 '16 at 11:06
  • 1
    `Dir::Tmpname.make_tmpname` was [removed in Ruby 2.5](https://medium.com/@maciejmensfeld/ruby-2-5-0-upgrade-remarks-100d7c0dd73d) – Abe Voelker May 27 '19 at 17:35
  • As of Ruby 2.4.4 this functionality no longer works, as make_tmpname doesn't take a directory anymore, just a filename prefix (directory separator characters are removed) – Brandon Aug 27 '19 at 11:01
  • is it just me, or is there no ruby docs for the built-in `Dir::Tmpname.create`? I can't find any! – jrochkind May 05 '20 at 18:22
  • Tempfile.new already uses Dir::Tmpname.create under the hood, so I'm not sure how explicitly using Dir::Tmpname.create gains anything different. The Ruby docs suggest using a mutex to protect the tempfile from multiple threads. Also, @jrochkind here is the source code for that method https://github.com/ruby/ruby/blob/2a02b61fae2c5dcfaf123f43c08c7c7949c1790c/lib/tmpdir.rb#L128 – chemturion Mar 31 '21 at 19:03
  • @chemturion Sometimes you need a temporary file name without the file having already been created. That's why I use this construct. – Wayne Conrad Jan 20 '23 at 20:42
3

Since Dir::Tmpname.make_tmpname was removed in Ruby 2.5.0, this one falls back to using SecureRandom:

require "tmpdir"

def generate_temp_filename(ext=".png")
  filename = begin
    Dir::Tmpname.make_tmpname(["x", ext], nil)
  rescue NoMethodError
    require "securerandom"
    "#{SecureRandom.urlsafe_base64}#{ext}"
  end
  File.join(Dir.tmpdir, filename)
end
Abe Voelker
  • 30,124
  • 14
  • 81
  • 98
1

Since you only need the filename, what about using the SecureRandom for that:

require 'securerandom'

filename =  "#{SecureRandom.hex(6)}.png" #=> "0f04dd94addf.png"

You can also use SecureRandom.alphanumeric

Paulo Fidalgo
  • 21,709
  • 7
  • 99
  • 115
-2

I found the Dir:Tmpname solution did not work for me. When evaluating this:

Dir::Tmpname.make_tmpname "/tmp/blob", nil

Under MRI Ruby 1.9.3p194 I get:

uninitialized constant Dir::Tmpname (NameError)

Under JRuby 1.7.5 (1.9.3p393) I get:

NameError: uninitialized constant Dir::Tmpname

You might try something like this:

def temp_name(file_name='', ext='', dir=nil)
    id   = Thread.current.hash * Time.now.to_i % 2**32
    name = "%s%d.%s" % [file_name, id, ext]
    dir ? File.join(dir, name) : name
end
dinman2022
  • 129
  • 1
  • 7
  • 4
    Before you used Dir::Tempname id you require 'tempfile' require 'tempfile' Dir::Tmpname.make_tmpname "/tmp/blob", nil If you haven't loaded 'Tempfile' then you won't be able to use it's extensions to Dir – Scott Thompson Mar 07 '14 at 16:34