Ruby 文件::目录?问题

发布于 2024-08-12 01:26:58 字数 1400 浏览 1 评论 0 原文

我对 ruby​​ 还很陌生,但到目前为止我非常喜欢它。有些事情给我带来了一些麻烦,以下也不例外。

我在这里想做的是通过对“Dir”进行子类化来创建一种“超级目录”。我添加了一个名为“subdirs”的方法,该方法旨在列出目录对象的文件,如果文件本身就是目录,则将它们推送到数组中。问题是,我的测试结果(File.directory?)很奇怪 - 这是我的方法代码:

  def subdirs
    subdirs = Array.new
    self.each do |x|
      puts "Evaluating file: #{x}"
      if File.directory?(x)
        puts "This file (#{x}) was considered a directory by File.directory?"
        subdirs.push(x)
        #yield(x) if block_given?
      end
    end
    return subdirs
  end

而且奇怪的是,即使我选择的目录(“/tmp”)中有很多目录 - 结果此调用仅列出“.”和“..”

puts "Testing new Directory custom class: FileOps/DirClass"

nd   = Directory.new("/tmp")
subs = nd.subdirs

结果:

Evaluating file: mapping-root
Evaluating file: orbit-jvxml
Evaluating file: custom-directory
Evaluating file: keyring-9x4JhZ
Evaluating file: orbit-root
Evaluating file: .
This file (.) was considered a directory by File.directory?
Evaluating file: .gdmFDB11U
Evaluating file: .X0-lock
Evaluating file: hsperfdata_mishii
Evaluating file: .X11-unix
Evaluating file: .gdm_socket
Evaluating file: ..
This file (..) was considered a directory by File.directory?
Evaluating file: .font-unix
Evaluating file: .ICE-unix
Evaluating file: ssh-eqOnXK2441
Evaluating file: vesystems-package
Evaluating file: mapping-jvxml
Evaluating file: hsperfdata_tomcat

I am quite new to ruby but enjoying it so far quite immensely. There are some things that have given me some trouble and the following is no exception.

What I am trying to do here is create a sort of 'super-directory' by sub-classing 'Dir'. I've added a method called 'subdirs' that is designed to list the directory object's files and push them into an array if the file is a directory itself. The issue is, the results from my test (File.directory?) is strange - here is my method code:

  def subdirs
    subdirs = Array.new
    self.each do |x|
      puts "Evaluating file: #{x}"
      if File.directory?(x)
        puts "This file (#{x}) was considered a directory by File.directory?"
        subdirs.push(x)
        #yield(x) if block_given?
      end
    end
    return subdirs
  end

And strangely, even though there are plenty of directories in the directory I've chosen ("/tmp") - the result of this call only lists "." and ".."

puts "Testing new Directory custom class: FileOps/DirClass"

nd   = Directory.new("/tmp")
subs = nd.subdirs

And the results:

Evaluating file: mapping-root
Evaluating file: orbit-jvxml
Evaluating file: custom-directory
Evaluating file: keyring-9x4JhZ
Evaluating file: orbit-root
Evaluating file: .
This file (.) was considered a directory by File.directory?
Evaluating file: .gdmFDB11U
Evaluating file: .X0-lock
Evaluating file: hsperfdata_mishii
Evaluating file: .X11-unix
Evaluating file: .gdm_socket
Evaluating file: ..
This file (..) was considered a directory by File.directory?
Evaluating file: .font-unix
Evaluating file: .ICE-unix
Evaluating file: ssh-eqOnXK2441
Evaluating file: vesystems-package
Evaluating file: mapping-jvxml
Evaluating file: hsperfdata_tomcat

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

丿*梦醉红颜 2024-08-19 01:26:58

您是否从 /tmp 中执行脚本?我的猜测(我没有尝试过)是 File.directory?(x) 正在测试当前目录中是否有一个名为 x 的目录 - 所以,如果您正在运行这个从另一个目录,您总是会找到 ...,但找不到其他目录。

尝试将 File.directory?(x) 更改为 File.directory?("#{path}/#{x}")

Are you executing the script from within /tmp? My guess (I haven't tried this) is that File.directory?(x) is testing to see if there's a directory named x in the current directory -- so, if you're running this from another directory, you'll always find . and .. but not the other directories.

Try changing File.directory?(x) to File.directory?("#{path}/#{x}").

没有心的人 2024-08-19 01:26:58

Mark Westling 已经回答了您直接的问题,但既然您提到您是 Ruby 新手,这里有一些其他风格建议:

def subdirs
  subdirs = Array.new

通常首选尽可能使用文字语法,因此表达这一点的常用方法是 子目录=[]

  self.each do |x|

self 是 Ruby 中的隐式接收者,因此您可以将其保留。 (毕竟,这不是 Python。)但是,代码的主要目的是通信,因此,如果您认为这可以更好地传达您的意图,请将其保留。

说到通信:x 不是除非您正在谈论笛卡尔坐标系中的点,否则这是一个非常具有沟通性的名称。 file 怎么样,或者如果您对目录也是文件的 Unix 概念感到不舒服,那么更中性的 entry 呢? (实际上,最好的可能是 path,但我们很快就会看到这可能会变得混乱。)

我的第三个建议实际上是个人偏好,与常见的 Ruby 风格相反:常见的 Ruby 风格规定单行块用 {/} 分隔,多行块用 do/end 分隔,就像你做到了。我不喜欢这样,因为这是一种任意的区别,没有传达任何含义。 (还记得“通信”的事情吗?)所以,我实际上做不同的事情:如果该块本质上是命令式的,例如,更改某些全局状态,我使用关键字(因为该块实际上是 doing some work),如果该块本质上是功能性的并且只是返回一个新对象,我更喜欢使用大括号(因为它们看起来很数学化)。所以,在这种情况下,我会使用大括号。

    if File.directory?(x)

正如 Mark 已经解释的那样,您需要在此处执行类似 File.directory?(File.join(path,entry)) 的操作,其中 Dir#pathDir 类返回 Dir.new 初始化时使用的路径。

在这里您还可以看到,为什么我们不使用 path 作为块参数的名称。否则我们需要编写File.directory?(File.join(self.path, path))

      subdirs.push(x)

在 Ruby 中将元素附加到数组或将几乎任何内容附加到任何内容的规范方法是 << 运算符。因此,这应该是 subdirs <<条目

Array#pushArray#<< 的别名,主要是为了让您将 Array 用作堆栈(在与 Array#pop 结合使用)。

    end
  end
  return subdirs

这是我的个人风格与常见 Ruby 风格之间的另一个脱节:在 Ruby 中,如果没有显式的 return,则返回值只是最后一个表达式的值。这意味着,您可以省略 return 关键字,而常见的 Ruby 风格表明您应该这样做。然而,我更喜欢使用类似于块分隔符的方式:对于本质上起作用的方法使用 return ,因为它们实际上“返回”一个值,而不使用 return对于命令式方法(因为它们的真正返回值不是return关键字之后的值,而是它们对环境产生的副作用)。因此,和您一样,我会在这里使用 return 关键字,尽管风格很常见。

通常,用空行将返回值与方法体的其余部分分隔开。 (顺便说一句,设置代码也是如此。)因此

end

,这就是我们现在所处的位置:

def subdirs
  subdirs = []

  each { |entry|
    if File.directory?(File.join(path, entry))
      subdirs << entry
    end
  }

  return subdirs
end

如您所见,if 表达式实际上仅用于跳过循环的一次迭代。使用 next 关键字可以更好地传达这一点:

def subdirs
  subdirs = []

  each { |entry|
    next  unless File.directory?(File.join(path, entry))
    subdirs << entry
  }

  return subdirs
end

如您所见,我们设法从块结构中删除一层嵌套,这始终是一个好兆头。

这种习惯用法被称为“保护子句”,在函数式编程中非常流行,许多语言甚至内置了保护结构,但它在地球上几乎任何其他语言中也大量使用,因为它极大地简化了控制流程:这个想法类似于城堡外的守卫:而不是让坏人进入城堡(方法、过程、函数、块……),然后痛苦地试图跟踪他们的一举一动并不断感到害怕如果你想错过某些东西或丢失它们,你只需在你的城堡的入口(你的路线的开始,街区,...)派一个守卫,他一开始就不让他们进去(这跳转到过程末尾,从方法中提前返回,跳过循环的一次迭代,...)。在 Ruby 中,您可以使用 raisereturnnextbreak 来实现此目的。在其他语言中,即使 GOTO 也可以(这是极少数情况之一,GOTO 实际上可以清理控制流)。

然而,我们可以通过识别迭代器模式来进一步简化:您获取一个列表(目录条目),然后将该列表“压缩”为单个对象(subdirs 数组)。对于范畴理论家来说,这尖叫着“变形”,对于铁杆函数式程序员来说,这是“fold”,对于软核函数式程序员来说,这是“reduce”,对于 Smalltalk 程序员来说,是“” >inject:into:" 和 Rubyist "Enumerable#inject":

def subdirs
  return inject([]) { |subdirs, entry|
    next subdirs  unless File.directory?(File.join(path, entry))
    subdirs << entry
  }
end

inject 使用上一次迭代的返回值来为下一次迭代提供种子,即为什么我们必须返回 subdirs,即使我们跳过迭代,通过使用 next subdirs 而不是普通的 next (这将返回 nil,这样在下一次迭代中 subdirs 将为 nil 并且 subdirs < 将引发 >NoMethodError。)

(请注意,我对块使用了花括号,即使该块实际上修改了其参数。不过,我觉得这仍然是一个“功能”块。YMMV。)

我们能做的最后一件事是要认识到我们所做的只是过滤(或者换句话说“选择”)数组的元素。 Ruby 已经内置了一个方法,它正是执行此操作:Enumerable#select。见证使用 Ruby 的全部功能所产生的单行奇迹:

def subdirs
  return select { |entry| File.directory?(File.join(path, entry)) }
end

作为一般提示:了解 Enumerable 的奇迹。它是 Ruby 编程的主力,类似于 .NET 中的 IEnumerable、Python 中的 dict、函数式语言或关联语言中的列表PHP 和 Perl 中的数组。

Mark Westling has already answered your immediate question, but since you mention you are new to Ruby, here are a couple of other style suggestions:

def subdirs
  subdirs = Array.new

It is generally preferred to use literal syntax where possible, so the common way to express this would be subdirs = [].

  self.each do |x|

self is the implicit receiver in Ruby, so you can just leave it off. (This is not Python, after all.) However, the primary purpose of code is communication, so if you believe, this communicates your intent better, leave it in.

Speaking of communication: x is not a terribly communicative name, unless you are talking about points in a cartesian coordinate system. How about file, or if you are uncomfortable with the Unix notion that directories are also files, the more neutral entry? (Actually, the best one would probably be path, but we will see soon how that might become confusing.)

My third suggestion is actually personal preference that is contrary to common Ruby style: common Ruby style dictates that one-line blocks are delimited with {/} and multiline blocks are delimited with do/end, just like you did. I don't like that, because it is an arbitrary distinction that doesn't convey any meaning. (Remember that "communication" thing?) So, I actually do things differently: if the block is imperative in nature and, for example, changes some global state, I use the keywords (because the block is actually doing some work) and if the block is functional in nature and just returns a new object, I prefer to use braces (because they look nicely mathematical). So, in this case, I would use braces.

    if File.directory?(x)

As Mark already explained, you need to do something like File.directory?(File.join(path, entry)) here, where Dir#path is a public attribute of the Dir class returning the path that Dir.new was initialized with.

Here you also see, why we didn't use path as the name of the block parameter. Otherwise we would need to write File.directory?(File.join(self.path, path)).

      subdirs.push(x)

The canonical way to append an element to an array, or indeed append pretty much anything to anything in Ruby, is the << operator. So, this should read subdirs << entry.

Array#push is an alias of Array#<<, which is mainly intended to let you use an Array as a stack (in conjunction with Array#pop).

    end
  end
  return subdirs

Here is another disconnect between my personal style and common Ruby style: in Ruby, if there is no explicit return, the return value is simply the value of the last expression. This means, you can leave off the return keyword and common Ruby style says you should. I, however, prefer to use this similar to the block delimiters: use return for methods that are functional in nature (because they actually "return" a value) and no return for imperative methods (because their real return value is not what comes after the return keyword, but what side-effects they have on the environment). So, like you, I would use a return keyword here, despite common style.

It is also customary, to seperate the return value from the rest of the method body by a blank line. (The same goes for setup code, by the way.)

end

So, this is where we stand right now:

def subdirs
  subdirs = []

  each { |entry|
    if File.directory?(File.join(path, entry))
      subdirs << entry
    end
  }

  return subdirs
end

As you can see, the if expression really only serves to skip one iteration of the loop. This is much better communicated by using the next keyword:

def subdirs
  subdirs = []

  each { |entry|
    next  unless File.directory?(File.join(path, entry))
    subdirs << entry
  }

  return subdirs
end

As you can see, we managed to remove one level of nesting from the block structure, which is always a good sign.

This idiom is called a "guard clause", and is quite popular in functional programming, where a lot of languages even have guard constructs built in, but it is also used heavily in pretty much any other language on the planet, because it greatly simplifies control flow: the idea is analogous to a guard posted outside a castle: instead of letting the bad guys into the castle (method, procedure, function, block, ...) and then painfully trying to track their every move and constantly being afraid to miss something or lose them, you simply post a guard at the entrance of your castle (the beginning of your method, block, ...) who doesn't let them in to begin with (which jumps to the end of the procedure, returns early from the method, skips one iteration of the loop, ...). In Ruby, you can use raise, return, next and break for this. In other languages, even GOTO is fine (this is one of those rare cases, where a GOTO can actually clear up control flow).

However, we can simplify this even further, by recognizing the iterator pattern: you take a list (the directory entries) and then you "squash" that list down to a single object (the subdirs array). To a category theorist, this screams "catamorphism", to a hardcore functional programmer "fold", to a softcore functional programmer "reduce", to a Smalltalk programmer "inject:into:" and to a Rubyist "Enumerable#inject":

def subdirs
  return inject([]) { |subdirs, entry|
    next subdirs  unless File.directory?(File.join(path, entry))
    subdirs << entry
  }
end

inject uses the return value of the previous iteration to seed the next one, that's why we have to return subdirs, even if we are skipping an iteration, by using next subdirs instead of plain next (which would return nil, so that on the next iteration subdirs would be nil and subdirs << entry would raise a NoMethodError.)

(Notice that I used curly braces for the block, even though the block actually modifies its argument. I feel this is still a "functional" block, though. YMMV.)

The last thing we can do is to recognize that what we are doing is just filtering (or in other words "selecting") elements of an array. And Ruby already has a method built in, which does exactly that: Enumerable#select. Witness the single-line awesomeness that using the full power of Ruby generates:

def subdirs
  return select { |entry| File.directory?(File.join(path, entry)) }
end

As a general tip: learn the wonders of Enumerable. It is the workhorse of Ruby programming, similar to IEnumerable<T> in .NET, dicts in Python, lists in functional languages or associative arrays in PHP and Perl.

风流物 2024-08-19 01:26:58

我做了一些小的改变......

class Directory < Dir
  def subdirs
    subdirs = []
    each do |x|
      puts "Evaluating file: #{x}"
      if File.directory? File.join path, x
        puts "This file (#{x}) was considered a directory by File.directory?"
        subdirs << x
        #yield(x) if block_given?
      end
    end
    subdirs
  end
end
puts "Testing new Directory custom class: FileOps/DirClass"

nd   = Directory.new "/tmp"
puts subs = nd.subdirs

I've made a few minor changes...

class Directory < Dir
  def subdirs
    subdirs = []
    each do |x|
      puts "Evaluating file: #{x}"
      if File.directory? File.join path, x
        puts "This file (#{x}) was considered a directory by File.directory?"
        subdirs << x
        #yield(x) if block_given?
      end
    end
    subdirs
  end
end
puts "Testing new Directory custom class: FileOps/DirClass"

nd   = Directory.new "/tmp"
puts subs = nd.subdirs
↙温凉少女 2024-08-19 01:26:58

将 * 替换为您想要的任何路径,然后就可以开始了。 Glob 使用 bash 通配符获取某个路径中的所有文件,因此您可以使用 * 和 ** 以及范围等。非常贴心。

select 的工作方式与拒绝相反,仅挑选 select 块中正确的值。

Dir.glob("*").select {|f|文件.目录?(f) }

Replace * with whatever path you want and you're good to go. Glob gets you all the files in some path using bash globbing so you can use * and ** as well as ranges, etc. Pretty sweet.

The select works like the opposite of reject, cherry-picking only the values that are true within the select block.

Dir.glob("*").select {|f| File.directory?(f) }

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文