我正在开发 一个 Ruby 脚本,特别是一个名为
copy2tmp
的方法,(复制粘贴的方法定义)
define_singleton_method(:copy2tmp) do |files|
files.each do |f|
if File.symlink?(f)
# Avoid trouble with symlink loops
# Delete old symlink if there is one, because:
# If a proper file or directory has been replaced with a symlink,
# remove the obsolete stuff.
# If there already is a symlink, delete because it might have been
# relinked.
if File.exist?("#{PARAMS[:tmpdir]}/#{f}")
FileUtils.rm("#{PARAMS[:tmpdir]}/#{f}")
end
# Create new symlink instead of copying
File.symlink("#{PARAMS[:jobpath]}/#{f}", "#{PARAMS[:tmpdir]}/#{f}")
elsif File.directory?(f)
FileUtils.mkdir_p("#{PARAMS[:tmpdir]}/#{f}")
copy2tmp(Dir.entries(f)\
.delete_if do |s| ['.', '..', ]\
.include?(s) end.map { |s| "#{f}/#{s}" })
# TODO: Is this necessary? Why not just copy? (For now, safer and more adaptable.)
else
FileUtils.cp(f,"#{PARAMS[:tmpdir]}/#{f}")
end
end
end
这似乎创建了过度深层的嵌套目录,从而导致错误。该脚本旨在使用
ltx2any
处理 LaTeX 文档。当脚本尝试将文件复制到临时目录时,就会出现问题,但最终会创建一个像 ../folderName/folderName/folderName/...
这样的深层嵌套结构。
copy2tmp(Dir.entries('.').delete_if { |f| exceptions.include?(f) })
copy2tmp
中的递归深度。这表明,每次递归调用,深度都会增加,目录路径也会变长 (../folderName/...
)。define_singleton_method(:copy2tmp) do |files, depth = 0|
puts "Debug: Recursion depth #{depth}, files: #{files.inspect}"
# ...
copy2tmp(Dir.entries(f).delete_if do |s| ['.', '..', ].include?(s) end.map { |s| "#{f}/#{s}" }, depth + 1)
# ...
copy2tmp(Dir.entries('.').delete_if { |f| exceptions.include?(f) }, 0)
手动检查:检查可能导致递归循环的符号链接或异常目录结构。这里没有发现任何明显的问题。我尝试在名称中带有或不带有空格的文件夹中运行脚本。
检查
copy2tmp
调用: 确保使用适当的参数调用 copy2tmp
并检查其递归调用。
期望与现实:我希望脚本将文件复制到临时目录,而不创建不必要的嵌套结构。相反,该脚本创建了一个深度嵌套的目录路径,导致“没有这样的文件或目录”错误。
我怀疑问题在于
copy2tmp
方法,无论是它的定义还是它的调用方式。该方法旨在将文件复制到临时目录中,但它似乎会导致目录创建的递归循环。任何人都可以帮助识别导致此问题的 copy2tmp
方法或其调用中的缺陷吗?任何有关停止这种意外递归的见解将不胜感激。
所以需要一些挖掘,但我找到了问题的症结。
您正在使用此行
exceptions = ignore + ignore.map { |s| "./#{s}" } + Dir['.*'] + Dir['./.*']
尝试过滤掉一些ignore
目录以及所有点文件。
在 Ruby 中,< 3.1
Dir['.*']
包括当前目录 ('.'
) 和父目录 ('..'
),因此此过滤器有效。
从 Ruby 3.1 开始
Dir['.*']
不再包含父目录 ('..'
)。看到这个提交
但是
Dir.entries('.')
确实包含父目录。
这意味着当你打电话时
Dir.entries('.').delete_if { |f| exceptions.include?(f) }
父目录将存在,并且不会从
files
数组中删除,因此您将父目录传递给该方法。由于此方法是递归的,并且在调用自身时会将当前目录附加到文件列表中,因此在 ruby 3.1+ 上运行此方法时,您会陷入永无止境的循环中。
虽然还有其他解决方案可以解决此问题,但最简单且向后兼容的方法是使用
Dir.children('.')
,因为 Dir::children
保证它不会包含 '.'
或 '..'
返回一个包含除“.”之外的所有文件名的数组。和给定目录中的“..”。