class Tempfile

用于管理临时文件的实用类。

有两种创建临时文件的方法

Tempfile.create 创建一个普通的 File 对象。文件删除的时间是可预测的。此外,它还支持打开并取消链接的技术,即在创建后立即删除临时文件。

Tempfile.newTempfile.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.newTempfile.open

本节不适用于 Tempfile.create,因为它返回的是 File 对象(而不是 Tempfile 对象)。

当你创建一个 Tempfile 对象时,它会创建一个具有唯一文件名的临时文件。Tempfile 对象的行为就像一个 File 对象,你可以在它上面执行所有常见的文件操作:读取数据、写入数据、更改其权限等等。因此,尽管此类没有明确记录 File 支持的所有实例方法,但实际上你可以在 Tempfile 对象上调用任何 File 实例方法。

Tempfile 对象有一个终结器来删除临时文件。这意味着临时文件是通过 GC 删除的。这可能会导致几个问题

  • 长时间的 GC 间隔和保守的 GC 可能会积累未删除的临时文件。

  • 如果 Ruby 异常退出(例如 SIGKILL、SEGV),则不会删除临时文件。

以下是 Tempfile.newTempfile.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

版本

公共类方法

create(basename="", tmpdir=nil, mode: 0, anonymous: false, **options, &block) 单击以切换源

在底层文件系统中创建一个文件;返回基于该文件的新 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>
    

使用参数 basenametmpdir,该文件在目录 tmpdir 中创建

Tempfile.create('foo', '.') # => #<File:./foo20220505-9795-1emu6g8>

关键字参数 modeoptions 直接传递给方法 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
new(basename="", tmpdir=nil, mode: 0, **options) 单击以切换源

在底层文件系统中创建一个文件;返回基于该文件的新 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>
    

使用参数 basenametmpdir,该文件在目录 tmpdir 中创建

Tempfile.new('foo', '.') # => #<Tempfile:./foo20220505-17839-xfstr8>

关键字参数 modeoptions 直接传递给方法 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

受保护的类方法

open(*args, **kw) { |tempfile| ... } 单击以切换源

创建一个新的 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

私有类方法

create_anonymous(basename="", tmpdir=nil, mode: 0, **options) { |tmpfile| ... } 单击以切换源
# 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
create_with_filename(basename="", tmpdir=nil, mode: 0, **options) { |tmpfile| ... } 点击以切换源代码
# 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

公共实例方法

close(unlink_now=false) 点击以切换源代码

关闭文件。如果 unlink_now 为 true,则文件将在关闭后被取消链接(删除)。当然,如果您现在不取消链接,您可以选择稍后调用 unlink

如果您没有显式取消链接临时文件,则删除操作将被延迟到对象被最终确定时。

# File tempfile.rb, line 279
def close(unlink_now=false)
  _close
  unlink if unlink_now
end
close!() 点击以切换源代码

关闭并取消链接(删除)文件。与调用 close(true) 具有相同的效果。

# File tempfile.rb, line 286
def close!
  close(true)
end
delete()
别名: unlink
length()
别名: size
open() 点击以切换源代码

以“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
path() 点击以切换源代码

返回临时文件的完整路径名。如果 unlink 已被调用,则此值为 nil。

# File tempfile.rb, line 341
def path
  @unlinked ? nil : __getobj__.path
end
size() 点击以切换源代码

返回临时文件的大小。作为副作用,在确定大小之前会刷新 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
也别名为: length