класс Tempfile
Утилитарный класс для управления временными файлами. При создании объекта Tempfile, он создаёт временный файл с уникальным именем. Объект Tempfile ведет себя как объект File, и вы можете выполнять все обычные операции с файлами: чтение данных, запись данных, изменение прав доступа и т.д. Таким образом, хотя этот класс не документирует все методы экземпляров, поддерживаемые File, вы можете вызывать любой метод экземпляра File на объекте Tempfile.
Синопсис
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
Рекомендации по использованию
Явное закрытие
Когда объект Tempfile попадает в сборщик мусора или когда интерпретатор Ruby завершает работу, связанный с ним временный файл автоматически удаляется. Это означает, что нет необходимости явным образом удалять Tempfile после использования, хотя это хорошая практика: не удаление неиспользуемых Tempfile может потенциально оставить большое количество временных файлов в файловой системе до их сбора мусором. Наличие этих временных файлов может затруднить определение нового имени файла Tempfile.
Поэтому следует всегда вызывать unlink или close в блоке ensure, например так:
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.open('foo') do |file|
# ...do something with file...
end
Удаление после создания
В системах POSIX можно удалить файл сразу после его создания и до закрытия. Это удаляет запись в файловой системе без закрытия дескриптора файла, гарантируя, что только процессы, которые уже открыли файл, могут получить доступ к его содержимому. Это настоятельно рекомендуется, если вы не хотите, чтобы другие процессы могли читать или записывать в Tempfile, и вам также не нужно знать имя Tempfile.
Например, практическим случаем использования unlink-after-creation может быть необходимость большого буфера байтов, слишком большого, чтобы комфортно поместиться в оперативной памяти, например, при написании веб-сервера, когда вы хотите буферизовать данные загружаемого клиентом файла.
Дополнительную информацию и пример кода см. в unlink.
Дополнительные замечания
Метод выбора имени файла Tempfile безопасен как для потоков, так и для межпроцессного взаимодействия: он гарантирует, что никакие другие потоки или процессы не выберут то же самое имя файла.
Tempfile сам по себе может быть не полностью потокобезопасным. Если вы обращаетесь к одному и тому же объекту Tempfile из нескольких потоков, то следует защитить его с помощью мьютекса.
Методы публичного класса
# File lib/tempfile.rb, line 349
def Tempfile.create(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 (а не Tempfile). Он не использует финализатор и делегирование, что делает его более эффективным и надёжным.
Если блок не задан, это аналогично Tempfile.new, за исключением создания объекта File вместо Tempfile. В этом случае созданный файл не удаляется автоматически. Для его удаления следует использовать File.unlink.
Если блок задан, то будет создан объект File, и блок будет вызван с объектом в качестве аргумента. Объект File будет автоматически закрыт, а временный файл удалён после завершения блока, освобождая все ресурсы, созданные блоком. Вызов возвращает значение блока.
В любом случае, все аргументы (basename, tmpdir, mode, и **options) будут обработаны так же, как и для Tempfile.new.
Tempfile.create('foo', '/home/temp') do |f|
# ... do something with f ...
end
# File lib/tempfile.rb, line 134
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
::Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts|
opts[:perm] = 0600
@tmpfile = File.open(tmpname, @mode, **opts)
@opts = opts.freeze
end
ObjectSpace.define_finalizer(self, Remover.new(@tmpfile))
super(@tmpfile)
end Создаёт временный файл с разрешениями 0600 (только чтение и запись владельцем) и открывает его в режиме “w+”.
Рекомендуется использовать Tempfile.create { … } по возможности, так как этот метод избегает затрат на делегирование и не полагается на финализатор для закрытия и удаления файла, что ненадежно.
Параметр basename используется для определения имени временного файла. Можно передать строку String или массив Array из 2 элементов типа String. В первом случае имя временного файла будет начинаться с переданной строки. Во втором случае – с первого элемента массива, а заканчиваться – вторым. Например:
file = Tempfile.new('hello')
file.path # => something like: "/tmp/hello2843-8392-92849382--0"
# Use the Array form to enforce an extension in the filename:
file = Tempfile.new(['hello', '.jpg'])
file.path # => something like: "/tmp/hello2843-8392-92849382--0.jpg"
Временный файл будет помещён в директорию, указанную параметром tmpdir. По умолчанию это Dir.tmpdir.
file = Tempfile.new('hello', '/home/aisaka')
file.path # => something like: "/home/aisaka/hello2843-8392-92849382--0"
Также можно передать хэш с опциями. Под капотом Tempfile создаёт временный файл с помощью File.open. Эти опции будут переданы в File.open. Это полезно в основном для указания кодировочных опций, например:
Tempfile.new('hello', '/home/aisaka', encoding: 'ascii-8bit')
# You can also omit the 'tmpdir' parameter:
Tempfile.new('hello', encoding: 'ascii-8bit')
Примечание: ключевой аргумент mode, принятый Tempfile, может быть только числовым, комбинацией режимов, определённых в File::Constants.
Исключения
Если Tempfile.new не сможет найти уникальное имя файла за ограниченное число попыток, будет возбуждено исключение.
# File lib/tempfile.rb, line 312
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.open всё ещё подходит, если вам нужен Tempfile, удаляемый финализатором, и вы не можете явно определить, где в программе Tempfile можно безопасно удалить.
Если блок не задан, это синоним для 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 168 def close(unlink_now=false) _close unlink if unlink_now end
Закрывает файл. Если unlink_now равно true, то файл будет удалён (удалён) после закрытия. Конечно, вы можете позже вызвать unlink, если не хотите удалять его сейчас.
Если вы не удалите временный файл явно, удаление будет отложено до финализации объекта.
# File lib/tempfile.rb, line 175 def close! close(true) end
Закрывает и удаляет (удаляет) файл. Имеет тот же эффект, что и вызов close(true).
# File lib/tempfile.rb, line 150 def open _close mode = @mode & ~(File::CREAT|File::EXCL) @tmpfile = File.open(@tmpfile.path, mode, **@opts) __setobj__(@tmpfile) end
Открывает или повторно открывает файл в режиме “r+”.
# File lib/tempfile.rb, line 228 def path @unlinked ? nil : @tmpfile.path end
Возвращает полное имя пути временного файла. Будет равно nil, если unlink был вызван.
# File lib/tempfile.rb, line 234
def size
if !@tmpfile.closed?
@tmpfile.size # File#size calls rb_io_flush_raw()
else
File.size(@tmpfile.path)
end
end Возвращает размер временного файла. В качестве побочного эффекта, буфер IO сбрасывается перед определением размера.
# File lib/tempfile.rb, line 212
def unlink
return if @unlinked
begin
File.unlink(@tmpfile.path)
rescue Errno::ENOENT
rescue Errno::EACCES
# may not be able to unlink on Windows; just ignore
return
end
ObjectSpace.undefine_finalizer(self)
@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
Ruby Core © 1993–2020 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.