为什么 Pathname.rmtree 在 Signal.trap('EXIT') 上失败



我正在尝试创建一个临时目录,该目录将在脚本退出时删除。

#!/usr/bin/env ruby
require 'pathname'
require 'tmpdir'
Tmp_dir = Pathname.new(Dir.mktmpdir)
Signal.trap('EXIT') {
Tmp_dir.rmtree
puts 'Doing cleanup'
}
puts 'Will exit after this message'

Doing cleanup消息永远不会触发,并且目录保持不变。经过一些测试,Tmp_dir.rmtree似乎从未运行过,之后也没有做任何事情

文档建议Pathname.rmtree调用FileUtils.rm_r,所以我这样尝试:

#!/usr/bin/env ruby
require 'fileutils'
require 'pathname'
require 'tmpdir'
Tmp_dir = Pathname.new(Dir.mktmpdir)
Signal.trap('EXIT') {
FileUtils.rm_r(Tmp_dir.to_path)
puts 'Doing cleanup'
}
puts 'Will exit after this message'

它是有效的。那么为什么rmtree版本没有呢?

信号陷阱与内核的At_Exit处理程序不相同

你在评论中说你不想使用Kernel#at_exit,但Signal#trap并不是真正的同义词。务实地说,这两种方法的行为明显不同。您可以很容易地看到以下内容:

# prints nil because variable not in scope; can't raise exceptions
Signal.trap(0) { p defined?(dir); p dir; raise dir }; dir="foo"; exit
# instance variables accessible; won't raise when undefined
Signal.trap(0) { p defined?(@dir); p @dir }; exit
# raises NameError because variable not in scope
at_exit { p defined?(dir); p dir }; dir="foo"; exit
# instance variable in scope, but won't raise when undefined
at_exit { p defined?(@dir); p @dir }; exit
# instance variable in scope; can raise manually
at_exit { p defined?(@dir); p @dir; raise @dir }; exit

如果你想知道为什么它们不同,你可能需要检查解析器或当前Ruby引擎的底层实现。这可能是一个bug,也可能是Ruby核心团队故意选择的。不管怎样,很明显,它们在Ruby 2.7.1中有不同的行为。

在At_Exit处理程序中使用实例变量

如前所述,使用带有捕获信号的局部变量会带来范围界定问题,并且您所需代码的其他实现可能对您的用例不可靠或不一致。您应该通过Kernel#at_exit注册一个处理程序,并将临时目录名存储在实例或类变量中。

@dir被定义为顶级作用域中的实例变量或处理程序的注册绑定时,通过Kernel#at_exit注册处理程序将执行您从irb或命令行期望的操作。路径名不是严格必需的,但它是为了匹配当前代码而包含的。

require 'pathname'
require 'tmpdir'
at_exit { @dir.rmtree }
@dir = Pathname.new Dir.mktmpdir
p @dir

使用块进行自动清理

通过使用Dir#mktmpdir命令的块形式,可以完全避免信号/退出处理程序中的范围问题。例如:

require 'tmpdir'
Dir.mktmpdir do |dir|
# do something with dir
end

这将在块退出时清理临时目录,而不必注册退出处理程序或信号陷阱。根据我的经验,这通常比延迟关闭更容易测试和调试,但您的里程肯定会有所不同。

最新更新