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 删除的。这可能会导致几个问题
-
长时间的 GC 间隔和保守的 GC 可能会积累未删除的临时文件。
-
如果 Ruby 异常退出(例如 SIGKILL、SEGV),则不会删除临时文件。
以下是 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 对象。
如果没有给出代码块且没有参数,则创建并返回文件,该文件的
-
类是 File(而不是 Tempfile)。
-
目录是系统临时目录(系统相关)。
-
生成的文件名在该目录中是唯一的。
-
权限为
0600
;请参阅 File。 -
模式为
'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 实现。O_TMPFILE 在 Linux 上使用。
相关:Tempfile.new
。
# File 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
在底层文件系统中创建一个文件;返回基于该文件的新 Tempfile 对象。
如果可能,请考虑改用 Tempfile.create
,它
-
避免了在
Tempfile.new
调用其超类DelegateClass(File)
时产生的委托性能成本。 -
不依赖于终结器来关闭和取消链接文件,这可能是不可靠的。
创建并返回文件,该文件的
-
类是 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 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
由终结器取消链接,并且你无法明确知道在程序中的哪个位置可以安全地取消链接 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 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
私有类方法
# File 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 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
公共实例方法
关闭文件。如果 unlink_now
为 true,则文件将在关闭后被取消链接(删除)。当然,如果您现在不取消链接,您可以选择稍后调用 unlink
。
如果您没有显式取消链接临时文件,则删除操作将被延迟到对象被最终确定时。
# File tempfile.rb, line 279 def close(unlink_now=false) _close unlink if unlink_now end
关闭并取消链接(删除)文件。与调用 close(true)
具有相同的效果。
# File tempfile.rb, line 286 def close! close(true) end
以“r+”模式打开或重新打开文件。
# File 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
返回临时文件的完整路径名。如果 unlink
已被调用,则此值为 nil。
# File tempfile.rb, line 341 def path @unlinked ? nil : __getobj__.path end
返回临时文件的大小。作为副作用,在确定大小之前会刷新 IO 缓冲区。
# File 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
从文件系统中取消链接(删除)文件。在使用文件后,应该始终取消链接该文件,正如 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
# File 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