Errno::EACCES 删除文件时(Windows 10)

Errno::EACCES upon deleting a file (on Windows 10)

有很多类似的问题,但 none 的答案对我有用。

我运行宁Rails6.0.3.2,Ruby2.6.6,以及 SQLite3 Windows 10 版本 2004 (19041.388)。我按照官方 Rails 站点上的 Getting Started guide 在 Rails 上安装了 Ruby,一切都应该是最新的。

我可以正常删除文件,而且我是用管理员帐户登录的——不是必须的。

我是 Ruby 和 Rails 的新手,非常感谢详细的回答。


代码

错误原因如下:

def destroy
    book = Book.find(params[:id])
    
    begin
        File.open(book.cover_url, 'w') do |f|
            File.delete(f)
        end
    rescue Errno::ENOENT
    end

    book.destroy
    redirect_to books_path
end

这是做什么的,它首先删除一本书的封面图像,然后从数据库中删除这本书本身。


错误

错误屏幕:

如果图片未加载,错误消息如下:

Errno::EACCES in BooksController#destroy

Permission denied @ apply2files - D:/Projects/Web/RoR/ecommerce/app/assets/images/covers/circles_scaling_anim_positioning.png

File.delete(f) 是罪魁祸首。


尝试过的解决方案


编辑: 我检查了有问题的文件,虽然它仍然存在,但现在是 0 字节大,所以我认为它被空数据覆盖了。然而,应该在 destroy 上执行的其余代码——即数据库中对象的破坏——似乎没有 运行,因为该对象仍然在那里。

将代码更改为...

begin
    File.delete(book.cover_url)
rescue Errno::ENOENT
end

...现在可以使用了。不打开任何文件;只是 File.delete(URL).


如果有人知道为什么原始版本不起作用,或者为什么它如此普遍被推荐,请post回答或对此回答发表评论。

与大多数 UNIXy 操作系统(例如 macOS、Linux 或各种 BSD)相比,操作系统在 Windows 上强制执行的文件处理不同。

在 UNIXy 操作系统上,文件系统中的文件条目只是指向存储在磁盘上的“真实”文件的指针。还有其他可能的指针,例如进程打开文件时的文件句柄,或硬链接(即指向完全相同文件的不同文件系统条目)。只要至少有一个指向该文件的有效指针,该文件就存在于磁盘上。

因此,在 UNIXy 系统上,您可以在从进程打开文件时从文件系统中删除(或重命名/移动)文件。只有在关闭最后一个文件句柄后,文件本身才会真正被删除。

Windows 默认情况下在这方面更为严格。只要有任何进程在文件上有文件句柄(即如果任何进程打开了文件),它就不允许删除文件。这解释了为什么当你有一个文件句柄时你不能删除文件

话虽如此,从 Ruby 2.3.0 开始,您可以在打开文件时设置一个标志,指示 Windows 允许在文件打开时删除(或移动)文件:

# the flags for the normal 'w' mode
file_mode = IO::WRONLY | IO::CREAT | IO::TRUNC

# Add flags to allow deleting (or moving) the opened file
file_mode |= IO::BINARY | IO::SHARE_DELETE

File.open(book.cover_url, mode: file_mode) do |f|
  File.delete(f)
end

请注意,IO::SHARE_DELETE 标志仅适用于以二进制模式(而不是文本模式)打开的文件,在处理此打开的文件时请考虑这一点。在 UNIXy 系统上,IO::SHARE_DELETE 被忽略。

some documentation 个可用的标志。

作为最后的评论:我假设您的代码是一个缩短的示例,除了删除它之外,您还遗漏了一些实际与打开的文件交互的代码。

如果您只想删除文件,则无需先打开它。只需使用 File.delete(book.cover_url) 将其删除即可。或者,如果您不关心任何错误(例如文件一开始就不存在,您也可以使用 FileUtils.rm_f(book.cover_url).