【问题标题】:Unable to force the removal of a directory无法强制删除目录
【发布时间】:2020-07-26 09:00:32
【问题描述】:

我在 Windows 10 上的 Ruby 脚本中使用 Info-ZIP 实用程序来解压缩存档、编辑内容并重新压缩它。该脚本旨在遍历一批档案,并删除提取内容时创建的临时文件夹。但是,该文件夹并未被删除。例如:

archives.each { |archive|
    system("unzip.exe -o archive -d temp")
    [...]
    system("zip.exe -X0q archive .")
    FileUtils.rm_rf "temp"
}

这在 Mac 上总是可以正常工作(使用相同的脚本,结合 zip/unzip 命令),但是,在 Windows 中,我无法删除临时文件夹。解压和压缩过程正常,但不会删除“temp”文件夹。这会导致解压缩实用程序抛出相同的错误:error: cannot delete old temp/[file] 对于文件夹中存在的每个文件。

我尝试过使用system("del /Q temp"),它会引发Could Not Find: C:\[...]\temp 错误,即使该目录确实存在。我尝试了system("rmdir /s /q temp"),它引发了另一个错误:The process cannot access the file because it is being used by another process. 不过,使用此文件的唯一“进程”是脚本本身。

脚本运行完成后,如果我之后运行FileUtils.rm_rf "temp",它就会运行,并成功删除目录。但是,我需要在每次迭代之后并在相同的原始脚本中完成此操作,以便在执行结束时正确覆盖和删除目录,而不会在命令提示符中出现任何错误或警告。

还有其他方法可以强制删除这个文件夹吗?

更新:在对脚本的不同部分进行了更多测试后,我能够找到问题的确切根源。所以所有的档案都包含 XHTML 文件。该脚本在某些情况下需要复制档案,并且复制的档案的内容被修改。是否需要复制取决于 XHTML 文件中是否存在某些标记。该脚本使用 Nokogiri 解析内容。似乎是通过 Nokogiri 解析的方法引发了这个问题。简化代码:

FileUtils.cp(original_archive,new_archive)
unzip_archive(new_archive) # a function to contain the unzipping steps
Dir.glob("temp/**/*.{html,xhtml}").each { |page|
        contents = Nokogiri::XML(open(page))
    }
zip_archive(new_archive)

在此示例中,实际上没有发生任何事情,但 Nokogiri::XML(open(page)) 的存在足以触发错误。通过 Nokogiri 打开的每个页面都会发生这种情况。因此,如果我将其更改为仅一页:

contents = Nokogiri::XML(open(Dir.glob("temp/**/one_page.xhtml")))

然后FileUtils.rm_rf 'temp' 成功删除了临时文件夹中的文件除了one_page.xhtml,这会引发“无法删除”错误。

有没有办法绕过这个问题,这样我仍然可以在我的 Ruby 脚本中使用 Nokogiri,但不会让脚本认为 Nokogiri“进程”仍在运行?这是 Windows 特有的,因为在 Mac 上没有遇到此类问题。

【问题讨论】:

  • zip 进程是否在 Windows 的后台运行?好像是这样的。如果您在尝试删除之前sleep 的时间足够长怎么办?
  • 使用Process.wait(Process.spawn('zip.exe -X0q archive .')) 等待 zip 命令退出。更多信息请访问ruby-doc.org/core-2.7.0/Process.html#method-c-spawn。无权访问 Windows,因此我无法验证它在该平台上是否可以正常工作,因此不作为答案发布。
  • Process.wait 确保在子进程退出之前控制权不会传递回父进程,因此您可以非常安全地依赖它。如果您不生成任何进程而是手动创建 temp 和单个文件,然后尝试使用 FileUtils.rm_rf 'temp' 删除它们会发生什么?
  • 这能回答你的问题吗? Ruby: Ensuring files are closed when reference is held by a different object?(注意接受答案的最后一行:Nokogiri 不会关闭打开的文件
  • 在 Ruby 中没有直接等效项:ruby-doc.org/core-2.4.4/File.html 您是否从两个答案中同时尝试了 File.openFile.read

标签: ruby windows zip nokogiri unzip


【解决方案1】:

看代码:

Dir.glob("temp/**/*.{html,xhtml}").each { |page|
        contents = Nokogiri::XML(open(page))
    }

这个问题看起来确实像您正在使用所有可用的文件句柄。这根本不是 Nokogiri 问题,只是发生问题时恰好在城里。

操作系统有一个可用的文件句柄池;它们不是无限的资源。如果你有大量的文件被找到,遍历它们并让它们保持打开状态,那么你就在消耗它们,这是糟糕的编程。

使用File.open 的块形式可以解决这个问题,但没有块的File.read 更简洁、更短,而且在我看来,这是一种更好的方法。

Dir.glob("temp/**/*.{html,xhtml}").each { |page|
  contents = Nokogiri::XML(File.read(page))
  # do something with contents
}

但是,使用Dir.glob 也会导致这个和另一个问题。您要求系统搜索磁盘以查找所有匹配的文件,然后将它们作为内存中的数组返回,然后对其进行迭代。相反,我强烈建议使用 Ruby 标准库中的Find。在这种情况下,它的表现要好得多。

Find 模块支持自顶向下遍历一组文件路径。

例如,计算主目录下所有文件的大小,忽略“点”目录中的任何内容(例如 $HOME/.ssh):

require 'find'

total_size = 0

Find.find(ENV["HOME"]) do |path|
  if FileTest.directory?(path)
    if File.basename(path).start_with?('.')
      Find.prune       # Don't look any further into this directory.
    else
      next
    end
  else
    total_size += FileTest.size(path)
  end
end

使用Find,您可以在包含数百万匹配项的巨大驱动器上运行代码,它的性能将优于Dir.glob

调整他们的示例,这段未经测试的代码应该可以帮助您入门:

require 'find'
require 'nokogiri'

Find.find('temp') do |path|
  if FileTest.file?(path) && path[/\.x?html$/i]
    contents = Nokogiri::XML(File.read(page))
    # do something with contents
  end
end

您经常会看到使用Dir.glob 进行自上而下搜索 (**) 的第二个问题是它会立即要求操作系统查找所有匹配的文件,然后等待操作系统收集它们.相反,如果您使用Find,您的代码将在每次搜索层次结构中的下一个匹配项时暂停,但这将是一个更短的暂停,从而导致响应更快的应用程序不会占用太多内存或击败磁盘收集文件。在远程安装的驱动器或文件服务器上,当系统管理员注意到巨大的网络和磁盘 IO 峰值而不是活动的轻微增加时,您最终可能会激怒他们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多