Ruby 文件::目录?问题
我对 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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您是否从
/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 thatFile.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)
toFile.directory?("#{path}/#{x}")
.Mark Westling 已经回答了您直接的问题,但既然您提到您是 Ruby 新手,这里有一些其他风格建议:
通常首选尽可能使用文字语法,因此表达这一点的常用方法是
子目录=[]
。self
是 Ruby 中的隐式接收者,因此您可以将其保留。 (毕竟,这不是 Python。)但是,代码的主要目的是通信,因此,如果您认为这可以更好地传达您的意图,请将其保留。说到通信:
x
不是除非您正在谈论笛卡尔坐标系中的点,否则这是一个非常具有沟通性的名称。file
怎么样,或者如果您对目录也是文件的 Unix 概念感到不舒服,那么更中性的entry
呢? (实际上,最好的可能是path
,但我们很快就会看到这可能会变得混乱。)我的第三个建议实际上是个人偏好,与常见的 Ruby 风格相反:常见的 Ruby 风格规定单行块用
{
/}
分隔,多行块用do
/end
分隔,就像你做到了。我不喜欢这样,因为这是一种任意的区别,没有传达任何含义。 (还记得“通信”的事情吗?)所以,我实际上做不同的事情:如果该块本质上是命令式的,例如,更改某些全局状态,我使用关键字(因为该块实际上是doing some work),如果该块本质上是功能性的并且只是返回一个新对象,我更喜欢使用大括号(因为它们看起来很数学化)。所以,在这种情况下,我会使用大括号。
正如 Mark 已经解释的那样,您需要在此处执行类似
File.directory?(File.join(path,entry))
的操作,其中Dir#path
是Dir
类返回Dir.new
初始化时使用的路径。在这里您还可以看到,为什么我们不使用
path
作为块参数的名称。否则我们需要编写File.directory?(File.join(self.path, path))
。在 Ruby 中将元素附加到数组或将几乎任何内容附加到任何内容的规范方法是
<<
运算符。因此,这应该是subdirs <<条目
。Array#push
是Array#<<
的别名,主要是为了让您将Array
用作堆栈(在与 Array#pop 结合使用)。这是我的个人风格与常见 Ruby 风格之间的另一个脱节:在 Ruby 中,如果没有显式的
return
,则返回值只是最后一个表达式的值。这意味着,您可以省略return
关键字,而常见的 Ruby 风格表明您应该这样做。然而,我更喜欢使用类似于块分隔符的方式:对于本质上起作用的方法使用 return ,因为它们实际上“返回”一个值,而不使用 return对于命令式方法(因为它们的真正返回值不是return
关键字之后的值,而是它们对环境产生的副作用)。因此,和您一样,我会在这里使用return
关键字,尽管风格很常见。通常,用空行将返回值与方法体的其余部分分隔开。 (顺便说一句,设置代码也是如此。)因此
,这就是我们现在所处的位置:
如您所见,
if
表达式实际上仅用于跳过循环的一次迭代。使用next
关键字可以更好地传达这一点:如您所见,我们设法从块结构中删除一层嵌套,这始终是一个好兆头。
这种习惯用法被称为“保护子句”,在函数式编程中非常流行,许多语言甚至内置了保护结构,但它在地球上几乎任何其他语言中也大量使用,因为它极大地简化了控制流程:这个想法类似于城堡外的守卫:而不是让坏人进入城堡(方法、过程、函数、块……),然后痛苦地试图跟踪他们的一举一动并不断感到害怕如果你想错过某些东西或丢失它们,你只需在你的城堡的入口(你的路线的开始,街区,...)派一个守卫,他一开始就不让他们进去(这跳转到过程末尾,从方法中提前返回,跳过循环的一次迭代,...)。在 Ruby 中,您可以使用
raise
、return
、next
和break
来实现此目的。在其他语言中,即使GOTO
也可以(这是极少数情况之一,GOTO
实际上可以清理控制流)。然而,我们可以通过识别迭代器模式来进一步简化:您获取一个列表(目录条目),然后将该列表“压缩”为单个对象(
subdirs
数组)。对于范畴理论家来说,这尖叫着“变形”,对于铁杆函数式程序员来说,这是“fold
”,对于软核函数式程序员来说,这是“reduce
”,对于 Smalltalk 程序员来说,是“” >inject:into:
" 和 Rubyist "Enumerable#inject
":inject
使用上一次迭代的返回值来为下一次迭代提供种子,即为什么我们必须返回subdirs
,即使我们跳过迭代,通过使用next subdirs
而不是普通的next
(这将返回nil
,这样在下一次迭代中subdirs
将为nil
并且subdirs < 将引发
>NoMethodError
。)(请注意,我对块使用了花括号,即使该块实际上修改了其参数。不过,我觉得这仍然是一个“功能”块。YMMV。)
我们能做的最后一件事是要认识到我们所做的只是过滤(或者换句话说“选择”)数组的元素。 Ruby 已经内置了一个方法,它正是执行此操作:
Enumerable#select
。见证使用 Ruby 的全部功能所产生的单行奇迹:作为一般提示:了解、Python 中的 dict、函数式语言或关联语言中的列表PHP 和 Perl 中的数组。
Enumerable
的奇迹。它是 Ruby 编程的主力,类似于 .NET 中的 IEnumerableMark Westling has already answered your immediate question, but since you mention you are new to Ruby, here are a couple of other style suggestions:
It is generally preferred to use literal syntax where possible, so the common way to express this would be
subdirs = []
.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 aboutfile
, or if you are uncomfortable with the Unix notion that directories are also files, the more neutralentry
? (Actually, the best one would probably bepath
, 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 withdo
/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 actuallydo
ing 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.As Mark already explained, you need to do something like
File.directory?(File.join(path, entry))
here, whereDir#path
is a public attribute of theDir
class returning the path thatDir.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 writeFile.directory?(File.join(self.path, path))
.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 readsubdirs << entry
.Array#push
is an alias ofArray#<<
, which is mainly intended to let you use anArray
as a stack (in conjunction withArray#pop
).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 thereturn
keyword and common Ruby style says you should. I, however, prefer to use this similar to the block delimiters: usereturn
for methods that are functional in nature (because they actually "return" a value) and noreturn
for imperative methods (because their real return value is not what comes after thereturn
keyword, but what side-effects they have on the environment). So, like you, I would use areturn
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.)
So, this is where we stand right now:
As you can see, the
if
expression really only serves to skip one iteration of the loop. This is much better communicated by using thenext
keyword: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
andbreak
for this. In other languages, evenGOTO
is fine (this is one of those rare cases, where aGOTO
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
":inject
uses the return value of the previous iteration to seed the next one, that's why we have to returnsubdirs
, even if we are skipping an iteration, by usingnext subdirs
instead of plainnext
(which would returnnil
, so that on the next iterationsubdirs
would benil
andsubdirs << entry
would raise aNoMethodError
.)(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:As a general tip: learn the wonders of
Enumerable
. It is the workhorse of Ruby programming, similar toIEnumerable<T>
in .NET,dict
s in Python, lists in functional languages or associative arrays in PHP and Perl.我做了一些小的改变......
I've made a few minor changes...
将 * 替换为您想要的任何路径,然后就可以开始了。 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) }