class Tempfile
一个用于管理临时文件的实用工具类。
有两种创建临时文件的方法:
-
Tempfile.create
(推荐) -
Tempfile.new
和Tempfile.open
(主要为了向后兼容,不推荐)
Tempfile.create
创建一个普通的 File 对象。文件删除的时间是可预测的。此外,它还支持创建后立即删除临时文件的打开并取消链接技术。
Tempfile.new
和 Tempfile.open
创建一个 Tempfile 对象。创建的文件由 GC
(终结器) 删除。文件删除的时间是不可预测的。
概要¶ ↑
require 'tempfile' # Tempfile.create with a block # The filename are choosen automatically. # (You can specify the prefix and suffix of the filename by an optional argument.) Tempfile.create {|f| f.puts "foo" f.rewind f.read # => "foo\n" } # The file is removed at block exit. # Tempfile.create without a block # You need to unlink the file in non-block form. f = Tempfile.create f.puts "foo" f.close File.unlink(f.path) # You need to unlink the file. # Tempfile.create(anonymous: true) without a block f = Tempfile.create(anonymous: true) # The file is already removed because anonymous. f.path # => "/tmp/" (no filename since no file) f.puts "foo" f.rewind f.read # => "foo\n" f.close # Tempfile.create(anonymous: true) with a block Tempfile.create(anonymous: true) {|f| # The file is already removed because anonymous. f.path # => "/tmp/" (no filename since no file) f.puts "foo" f.rewind f.read # => "foo\n" } # Not recommended: Tempfile.new without a block 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
关于 Tempfile.new
和 Tempfile.open
¶ ↑
本节不适用于 Tempfile.create
,因为它返回一个 File
对象 (而不是 Tempfile
对象)。
当您创建一个 Tempfile
对象时,它将创建一个具有唯一文件名的临时文件。Tempfile
对象的行为就像一个 File
对象一样,您可以在其上执行所有常用的文件操作:读取数据、写入数据、更改其权限等等。因此,尽管此类没有明确记录 File
支持的所有实例方法,但实际上您可以对 Tempfile
对象调用任何 File
实例方法。
Tempfile
对象有一个终结器来删除临时文件。这意味着临时文件是通过 GC
删除的。这可能会导致几个问题:
以下是关于 Tempfile.new
和 Tempfile.open
的传统最佳实践。
显式关闭¶ ↑
当 Tempfile
对象被垃圾回收,或者当 Ruby 解释器退出时,其关联的临时文件会自动删除。这意味着没有必要在使用后显式删除 Tempfile
,尽管这样做是一个好习惯:不显式删除未使用的 Tempfile 可能会在文件系统上留下大量临时文件,直到它们被垃圾回收。这些临时文件的存在会使确定新的 Tempfile
文件名更加困难。
因此,应该始终在 ensure 代码块中调用 unlink
或 close,如下所示:
file = Tempfile.new('foo') begin # ...do something with file... ensure file.close file.unlink # deletes the temp file end
Tempfile.create
{ … } 就是为此目的而存在的,并且使用起来更方便。请注意,Tempfile.create
返回 File
实例,而不是 Tempfile
,这也避免了委托的开销和复杂性。
Tempfile.create('foo') do |file| # ...do something with file... end
创建后取消链接¶ ↑
在 POSIX 系统上,可以在创建文件后立即取消链接,并在关闭文件之前进行。这会删除文件系统条目,而不会关闭文件句柄,因此它确保只有已经打开文件句柄的进程才能访问文件的内容。强烈建议您这样做,如果您不希望任何其他进程能够读取或写入 Tempfile
,并且您也不需要知道 Tempfile 的文件名。
此外,这保证了即使 Ruby 异常退出,临时文件也会被删除。当文件关闭或 Ruby 进程退出 (正常或异常) 时,操作系统会回收临时文件的存储空间。
例如,创建后取消链接的一个实际用例是:您需要一个太大的字节缓冲区,无法舒适地放入 RAM 中,例如,当您正在编写 Web 服务器并且想要缓冲客户端的文件上传数据时。
‘Tempfile.create(anonymous: true)` 支持此行为。它也适用于 Windows。
小提示¶ ↑
Tempfile 的文件名选择方法既是线程安全的,又是进程间安全的:它保证没有其他线程或进程会选择相同的文件名。
然而,Tempfile
本身可能并非完全线程安全的。如果您从多个线程访问同一个 Tempfile
对象,则应使用互斥锁保护它。
常量
- VERSION
-
版本
公共类方法
源代码
# File lib/tempfile.rb, line 558 def Tempfile.create(basename="", tmpdir=nil, mode: 0, anonymous: false, **options, &block) if anonymous create_anonymous(basename, tmpdir, mode: mode, **options, &block) else create_with_filename(basename, tmpdir, mode: mode, **options, &block) end end
在底层文件系统中创建一个文件;返回基于该文件的新 File 对象。
如果没有给定块并且没有参数,则创建并返回文件,其
-
目录是系统临时目录 (系统相关)。
-
生成的文件名在该目录中是唯一的。
-
权限为
0600
;请参阅 文件权限。 -
模式为
'w+'
(读/写模式,定位在末尾)。
临时文件的删除取决于关键字参数 anonymous
以及是否给定了块。请参阅稍后关于 anonymous
关键字参数的描述。
示例
f = Tempfile.create # => #<File:/tmp/20220505-9795-17ky6f6> f.class # => File f.path # => "/tmp/20220505-9795-17ky6f6" f.stat.mode.to_s(8) # => "100600" f.close File.exist?(f.path) # => true File.unlink(f.path) File.exist?(f.path) # => false Tempfile.create {|f| f.puts "foo" f.rewind f.read # => "foo\n" f.path # => "/tmp/20240524-380207-oma0ny" File.exist?(f.path) # => true } # The file is removed at block exit. f = Tempfile.create(anonymous: true) # The file is already removed because anonymous f.path # => "/tmp/" (no filename since no file) f.puts "foo" f.rewind f.read # => "foo\n" f.close Tempfile.create(anonymous: true) {|f| # The file is already removed because anonymous f.path # => "/tmp/" (no filename since no file) f.puts "foo" f.rewind f.read # => "foo\n" }
如果给定参数 basename
,则可以是以下之一:
-
一个字符串:生成的文件名以
basename
开头Tempfile.create('foo') # => #<File:/tmp/foo20220505-9795-1gok8l9>
-
一个包含两个字符串的数组
[prefix, suffix]
:生成的文件名以prefix
开头,以suffix
结尾Tempfile.create(%w/foo .jpg/) # => #<File:/tmp/foo20220505-17839-tnjchh.jpg>
使用参数 basename
和 tmpdir
,该文件在目录 tmpdir
中创建
Tempfile.create('foo', '.') # => #<File:./foo20220505-9795-1emu6g8>
关键字参数 mode
和 options
直接传递给方法 File.open
-
为
mode
给定的值必须是整数,并且可以表示为File::Constants
中定义的常数的逻辑 OR。 -
对于
options
,请参阅 打开选项。
关键字参数 anonymous
指定何时删除文件。
-
不带块的
anonymous=false
(默认):不会删除该文件。 -
带块的
anonymous=false
(默认):在块退出后删除该文件。 -
不带块的
anonymous=true
:在返回之前删除该文件。 -
带块的
anonymous=true
:在调用该块之前删除该文件。
在第一种情况下 (不带块的 anonymous=false
),不会自动删除该文件。应该显式关闭它。它可以用于重命名为所需的文件名。如果不需要该文件,则应该显式删除它。
当 anonymous
为 true 时,创建的文件对象的 File#path
方法返回带有尾部斜杠的临时目录。
当给定块时,它会如上所述创建文件,将其传递给该块,并返回该块的值。在返回之前,将关闭文件对象并删除底层文件
Tempfile.create {|file| file.path } # => "/tmp/20220505-9795-rkists"
实现说明
关键字参数 +anonymous=true+ 在 Windows 上使用 FILE_SHARE_DELETE 实现。在 Linux 上使用 O_TMPFILE。
相关:Tempfile.new
。
源代码
# File lib/tempfile.rb, line 219 def initialize(basename="", tmpdir=nil, mode: 0, **options) warn "Tempfile.new doesn't call the given block.", uplevel: 1 if block_given? @unlinked = false @mode = mode|File::RDWR|File::CREAT|File::EXCL tmpfile = nil ::Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts| opts[:perm] = 0600 tmpfile = File.open(tmpname, @mode, **opts) @opts = opts.freeze end super(tmpfile) @finalizer_manager = FinalizerManager.new(__getobj__.path) @finalizer_manager.register(self, __getobj__) end
在底层文件系统中创建一个文件;返回基于该文件的新 Tempfile 对象。
如果可能,请考虑改用 Tempfile.create
,它
-
避免了
Tempfile.new
调用其父类DelegateClass(File)
时产生的委托性能开销。 -
不依赖于终结器来关闭和取消链接文件,这可能不可靠。
创建并返回文件,其
-
Class
是 Tempfile (而不是 File,如Tempfile.create
中)。 -
目录是系统临时目录 (系统相关)。
-
生成的文件名在该目录中是唯一的。
-
权限为
0600
;请参阅 文件权限。 -
模式为
'w+'
(读/写模式,定位在末尾)。
当 Tempfile 对象消亡并被垃圾回收器回收时,底层文件会被删除。
示例
f = Tempfile.new # => #<Tempfile:/tmp/20220505-17839-1s0kt30> f.class # => Tempfile f.path # => "/tmp/20220505-17839-1s0kt30" f.stat.mode.to_s(8) # => "100600" File.exist?(f.path) # => true File.unlink(f.path) # File.exist?(f.path) # => false
如果给定参数 basename
,则可以是以下之一:
-
一个字符串:生成的文件名以
basename
开头Tempfile.new('foo') # => #<Tempfile:/tmp/foo20220505-17839-1whk2f>
-
一个包含两个字符串的数组
[prefix, suffix]
:生成的文件名以prefix
开头,以suffix
结尾Tempfile.new(%w/foo .jpg/) # => #<Tempfile:/tmp/foo20220505-17839-58xtfi.jpg>
使用参数 basename
和 tmpdir
,该文件在目录 tmpdir
中创建
Tempfile.new('foo', '.') # => #<Tempfile:./foo20220505-17839-xfstr8>
关键字参数 mode
和 options
直接传递给方法 File.open
-
使用
mode
给定的值必须是整数,并且可以表示为File::Constants
中定义的常数的逻辑 OR。 -
对于
options
,请参阅 打开选项。
相关:Tempfile.create
。
源代码
# File lib/tempfile.rb, line 439 def open(*args, **kw) tempfile = new(*args, **kw) if block_given? begin yield(tempfile) ensure tempfile.close end else tempfile end end
创建一个新的 Tempfile
。
不推荐使用此方法,它主要为了向后兼容而存在。请改用 Tempfile.create
,它可以避免委托的成本,不依赖于终结器,并且在给定块时还会取消链接文件。
如果您需要 Tempfile
由终结器取消链接,并且您不能明确知道在程序中的哪个位置可以安全地取消链接 Tempfile
,那么 Tempfile.open
仍然是合适的。
如果没有给定块,则它是 Tempfile.new
的同义词。
如果提供了代码块,则会创建一个 Tempfile
对象,并使用该 Tempfile
对象作为参数运行该代码块。代码块终止后,Tempfile
对象将自动关闭。但是,该文件不会被取消链接,需要使用 Tempfile#close!
或 Tempfile#unlink
手动取消链接。终结器将尝试取消链接,但不应依赖它,因为它可能使文件在磁盘上保留的时间比预期的要长得多。例如,在 CRuby 上,由于保守的堆栈扫描和未使用内存中留下的引用,终结器可能会被延迟。
该调用返回代码块的值。
在任何情况下,所有参数 (*args
) 都将传递给 Tempfile.new
。
Tempfile.open('foo', '/home/temp') do |f| # ... do something with f ... end # Equivalent: f = Tempfile.open('foo', '/home/temp') begin # ... do something with f ... ensure f.close end
私有类方法
源代码
# File lib/tempfile.rb, line 606 def create_anonymous(basename="", tmpdir=nil, mode: 0, **options, &block) tmpfile = nil tmpdir = Dir.tmpdir() if tmpdir.nil? if defined?(File::TMPFILE) # O_TMPFILE since Linux 3.11 begin tmpfile = File.open(tmpdir, File::RDWR | File::TMPFILE, 0600) rescue Errno::EISDIR, Errno::ENOENT, Errno::EOPNOTSUPP # kernel or the filesystem does not support O_TMPFILE # fallback to create-and-unlink end end if tmpfile.nil? mode |= File::SHARE_DELETE | File::BINARY # Windows needs them to unlink the opened file. tmpfile = create_with_filename(basename, tmpdir, mode: mode, **options) File.unlink(tmpfile.path) tmppath = tmpfile.path end path = File.join(tmpdir, '') unless tmppath == path # clear path. tmpfile.autoclose = false tmpfile = File.new(tmpfile.fileno, mode: File::RDWR, path: path) PathAttr.set_path(tmpfile, path) if defined?(PathAttr) end if block begin yield tmpfile ensure tmpfile.close end else tmpfile end end
源代码
# File lib/tempfile.rb, line 567 def create_with_filename(basename="", tmpdir=nil, mode: 0, **options) tmpfile = nil Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts| mode |= File::RDWR|File::CREAT|File::EXCL opts[:perm] = 0600 tmpfile = File.open(tmpname, mode, **opts) end if block_given? begin yield tmpfile ensure unless tmpfile.closed? if File.identical?(tmpfile, tmpfile.path) unlinked = File.unlink tmpfile.path rescue nil end tmpfile.close end unless unlinked begin File.unlink tmpfile.path rescue Errno::ENOENT end end end else tmpfile end end
公共实例方法
源代码
# File lib/tempfile.rb, line 279 def close(unlink_now=false) _close unlink if unlink_now end
关闭文件。如果 unlink_now
为 true,则在关闭后将取消链接(删除)该文件。当然,如果您现在不取消链接,您可以选择稍后调用 unlink
。
如果您没有显式取消链接临时文件,则删除操作将延迟到对象被终结时。
源代码
# File lib/tempfile.rb, line 286 def close! close(true) end
关闭并取消链接(删除)该文件。与调用 close(true)
的效果相同。
源代码
# File lib/tempfile.rb, line 257 def open _close mode = @mode & ~(File::CREAT|File::EXCL) __setobj__(File.open(__getobj__.path, mode, **@opts)) @finalizer_manager.register(self, __getobj__) __getobj__ end
以 “r+” 模式打开或重新打开文件。
源代码
# File lib/tempfile.rb, line 341 def path @unlinked ? nil : __getobj__.path end
返回临时文件的完整路径名。如果调用了 unlink
,则此值将为 nil。
源代码
# File lib/tempfile.rb, line 347 def size if !__getobj__.closed? __getobj__.size # File#size calls rb_io_flush_raw() else File.size(__getobj__.path) end end
返回临时文件的大小。作为副作用,在确定大小之前,会刷新 IO
缓冲区。
源代码
# File lib/tempfile.rb, line 323 def unlink return if @unlinked begin File.unlink(__getobj__.path) rescue Errno::ENOENT rescue Errno::EACCES # may not be able to unlink on Windows; just ignore return end @finalizer_manager.unlinked = true @unlinked = true end
从文件系统中取消链接(删除)该文件。在使用完文件后,应始终取消链接该文件,如 Tempfile
概述中的“显式关闭”最佳实践部分所述
file = Tempfile.new('foo') begin # ...do something with file... ensure file.close file.unlink # deletes the temp file end
关闭前取消链接¶ ↑
在 POSIX 系统上,可以在关闭文件之前取消链接它。此实践在 Tempfile
概述(“创建后取消链接”部分)中详细说明;请参阅该部分以获取更多信息。
但是,非 POSIX 操作系统可能不支持关闭前取消链接。最值得注意的是 Microsoft Windows:取消链接一个未关闭的文件将导致错误,此方法将静默忽略该错误。如果您希望尽可能地练习关闭前取消链接,那么您应该编写如下代码
file = Tempfile.new('foo') file.unlink # On Windows this silently fails. begin # ... do something with file ... ensure file.close! # Closes the file handle. If the file wasn't unlinked # because #unlink failed, then this method will attempt # to do so again. end