Create a Tempfile Without Opening It in Ruby

Create a tempfile without opening it in Ruby

Is using FileUtils.touch acceptable solution? You can touch a file and delete it once you are done with whatever you want.

Is there any way to make a Ruby temporary file permanent?

Not really. For the question itself, see this:

ObjectSpace.undefine_finalizer(tmpfile)

The Tempfile library uses Ruby ObjectSpace finalizers to automatically delete itself on garbage collection. By using the above line you can remove the Tempfile's ability to delete itself if you don't delete it. So, for example:

$ irb
2.0.0p0 :001 > require "tempfile"
=> true
2.0.0p0 :002 > t = Tempfile.new("test")
=> #<Tempfile:/tmp/test20140122-6655-80p4b7>
2.0.0p0 :003 > t.write("Hi!")
=> 3
2.0.0p0 :004 > ObjectSpace.undefine_finalizer(t)
=> #<Tempfile:/tmp/test20140122-6655-80p4b7>
2.0.0p0 :005 > exit
$ cat /tmp/test20140122-6655-80p4b7
Hi!
$

There's something else to be aware of though. Tempfile will use system temporary file directories like /tmp that the OS automatically cleans out every once in a while (for example on every boot). Because of this, even if you "persist" the file, you either need to be OK with it disappearing, or move it to a directory that doesn't get cleaned out by default, like /var/tmp (the Linux directory for persistant temporary files).


As for your second question, try this code from here:

Dir::Tmpname.create('your_application_prefix') { |path| puts path }

It requires a require "tmpdir".

Ruby how to write to Tempfile?

You're going to want to close the temp file after writing to it. Just add a t.close to the end. I bet the file has buffered output.

Creating a thread-safe temporary file name

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"

How to generate a temporary file for use in a unit test?

The .create and .new methods of Ruby's Tempfile take a second argument which is the directory you want the file to be in, and files created by Tempfile are automatically deleted when the temporary file's File object is garbage-collected or Ruby exits. So you can create a temporary file,

tempfile = Tempfile.new('filename', 'directoryname')

write to it, do your test and let Ruby clean it up for you.

Note that the first argument is not the entire unqualified name of the file, but a part to which Tempfile adds disambiguating characters, so you can safely do it more than once in a test suite.

Also, if you need the file to have a particular suffix, you can do e.g.

tempfile = Tempfile.new(['filename', '.rb'], 'directoryname')

ruby Temporary files inside temporary directory

You should use the Tempfile class.

require 'tempfile'

file = Tempfile.new('foo')
file.path # => A unique filename in the OS's temp directory,
# e.g.: "/tmp/foo.24722.0"
# This filename contains 'foo' in its basename.
file.write("hello world")
file.rewind
file.read # => "hello world"
file.close
file.unlink # deletes the temp file

To create temporary folders, you can use Dir.mktmpdir.

Ruby auto deleting temp file?

From docs:

When a Tempfile object is garbage collected, or when the Ruby interpreter exits, its associated temporary file is automatically deleted.

So, as long as you have your f in scope, it will not be deleted. If you exit Ruby, it will be deleted. If you are still in Ruby but f has fallen out of scope, it is indeterminate (probably not deleted, but not guaranteed to exist, and should not be used.)

Weird Ruby IO with Tempfile

This is a problem of buffering. You need to flush the IO buffer to disk before trying to read it. Either file.close (if you've finished with it) or file.flush before doing the File.open for the read.

Update

I hadn't thought about this, but you don't need to reopen the temp file just to read it. It's already open for writing and reading, all you need to do is seek to the start of the file before reading. This way you don't have to do the flush (because you're actually reading from the buffer)...

# starting partway into your code...
num_bytes_written = file.write(uri.read)
puts "Wrote #{num_bytes_written} bytes"

puts "No need to open #{file.path} >>"
puts "### BEGINNING OF FILE ###"

file.rewind # set the cursor to the start of the buffer
puts file.read # cursor is back at the end of the buffer now

puts "### END OF FILE ###"

Another Update

After a comment from @carp I have adjusted the code above to use rewind instead of seek 0 because it also resets lineno to 0 (and not having that done, if you were using lineno would be very confusing). Also actually it's a more expressive method name.



Related Topics



Leave a reply



Submit