# **第 18 章 File 类与 Dir 类**
在第 17 章中,我们介绍了如何读写文件中数据。由于 `IO` 类是 `File` 类的父类,因此在 `File` 类中就可以使用 `IO` 类中操作文件的方法。
在本章中,我们将会学习文件名及属性等表面操作,同时也会介绍如何操作文件(数据容器)以及目录(文件容器)等。
与文件以及目录相关的操作一般有以下几种:
-
**文件的操作**
名称的变更、拷贝、删除等基本操作。
-
**目录的操作**
目录的引用、创建、删除等操作。
-
**属性的操作**
“只读”等文件属性的操作。
我们日常在使用计算机的时候,在操作大量文件时,可能都会感到麻烦,甚至还会造成操作失误等。而如果将对大量数据进行的单调操作通过程序来处理的话,就不仅可以提高处理速度,而且还可以避免错误。例如,像只是单纯地变更文件名这种程度的操作,只需几行代码就可以轻松搞定,而且这次写好的程序在以后还可以多次使用。因此我们应该积极地使这种简单的操作变得自动化。不过,在 Windows、Mac OS 平台下,目录被称为文件夹,这一点还请读者们注意。
### **18.1 File 类**
`File` 类中实现了操作文件系统的方法。
### **18.1.1 变更文件名**
-
**`File.rename`(*before, after*)**
我们可以用 `File.rename` 方法变更文件名。
~~~
File.rename("before.txt", "after.txt")
~~~
还可以将文件移动到已存在的目录下,目录不存在则程序会产生错误。
~~~
File.rename("data.txt", "backup/data.txt")
~~~
> **注** `File.rename` 方法无法跨文件系统或者驱动器移动文件。
如果文件不存在,或者没有适当的文件操作权限等,则文件操作失败,程序抛出异常。
> **执行示例**
~~~
> irb --simple-prompt
>> File.open("/no/such/file")
Errno::ENOENT: No such file or directory - /no/such/file
from (irb):1:in `initialize'
from (irb):1:in `open'
from (irb):1
from /usr/bin/irb:12:in `<main>'
~~~
### **18.1.2 复制文件**
只用一个 Ruby 预定义的方法是无法复制文件的。这时,我们可以利用 `File.open` 方法与 `write` 方法的组合来实现文件复制。
~~~
def copy(from, to)
File.open(from) do |input|
File.open(to, "w") do |output|
output.write(input.read)
end
end
end
~~~
不过,由于文件复制是常用的操作,如果每次使用时都需要自己重新定义一次的话就非常麻烦。因此,我们可以通过引用 `fileutils` 库,使用其中的 `FileUtils.cp`(文件拷贝)、`FileUtils.mv`(文件移动)等方法来操作文件。
~~~
require "fileutils"
FileUtils.cp("data.txt", "backup/data.txt")
FileUtils.mv("data.txt", "backup/data.txt")
~~~
`File.rename` 不能实现的跨文件系统、驱动器的文件移动,用 `FileUtils.mv` 方法则可以轻松实现。关于 `fileutils` 库,我们在后续章节中会详细介绍。
### **18.1.3 删除文件**
-
**`File.delete`(*file*)
`File.unlink`(*file*)**
我们可以使用 `File.delete` 方法或 `File.unlink` 方法删除文件。
~~~
File.delete("foo")
~~~
### **18.2 目录的操作**
`Dir` 类中实现了目录相关的操作方法。在详细说明前,我们先来复习一下有关目录的一些基础知识。
目录中可以存放多个文件。除了文件之外,目录中还可以存放其他目录,其他目录中又可以再存放目录……如此无限循环。通过将多个目录进行排列、嵌套,就可以轻松地管理大量的文件。
Windows 的资源管理器(图 18.1)的左侧就是可视化的目录层次结构(树形结构)。用目录名连接 / 的方法即可指定目录中的文件。由于我们可以通过目录名指定文件的位置,因此表示文件位置的目录名就称为路径(path)或路径名。另外,我们把目录树的起点目录称为根目录(root directory),根目录只用 / 表示。
![{%}](https://box.kancloud.cn/2015-10-26_562e020423d6f.png)
**图 18.1 Windows 的资源管理器**
> **专栏**
> **关于 Windows 的路径名**
> 在 Windows 的命令行中,目录分隔符用的是 \。由于使用 \ 后不仅会使字符串难以读懂,而且也不能直接在 Unix 中执行同一个程序,因此还是建议大家尽量使用 /。但有一点请大家注意,像 WIN32OLE 这样使用 Windows 特有的功能时,使用 / 后可能会使程序变得无法执行。
> 在 Windows 中,驱动器是目录的上层文件管理单位。 一般用 1 个英文字母(盘符)表示与之相对应的驱动器,如 A: 表示软盘,C:、D:……表示硬盘。这种情况下,请读者把各驱动器当成独立的根目录来看待。例如,很明显地 C:/ 与 D:/ 表示的是不同的驱动器,但如果只写 / 的话,程序执行位置的不同,其表示的驱动器也不同,继而所表示的目录也不同。
-
**`Dir.pwd`
`Dir.chdir`(*dir*)**
程序可以获取运行时所在的目录信息,即当前目录(current directory)。使用 `Dir.pwd` 方法获取当前目录,变更当前目录使用 `Dir.chdir` 方法。我们可以对 `Dir.chdir` 方法的参数 dir 指定相对与当前目录的相对路径,也可以指定相对于根目录的绝对路径。
~~~
p Dir.pwd #=> "/usr/local/lib"
Dir.chdir("ruby/2.0.0") #=> 根据相对路径移动
p Dir.pwd #=> "/usr/local/lib/ruby/2.0.0"
Dir.chdir("/etc") #=> 根据绝对路径移动
p Dir.pwd #=> "/etc"
~~~
当前目录下的文件,我们可以通过指定文件名直接打开,但如果变更了当前目录,则还需要指定目录名。
~~~
p Dir.pwd #=> "/usr/local/lib/ruby/2.0.0"
io = File.open("find.rb")
#=> 打开"/usr/local/lib/ruby/2.0.0/find.rb"
io.close
Dir.chdir("../..") # 移动到上两层的目录中
p Dir.pwd #=> "/usr/local/lib"
io = File.open("ruby/2.0.0/find.rb")
#=> 打开"/usr/local/lib/ruby/2.0.0/find.rb"
io.close
~~~
### **18.2.1 目录内容的读取**
像介绍文件的时候一样,我们先来了解一下如何读取已存在的目录。读取目录内容的方法与读取文件的方法基本上是一样的。
① **打开目录**
② **读取内容**
③ **关闭**
-
**`Dir.open`(*path*)
`Dir.close`**
与 `File` 类一样,`Dir` 类也有 `open` 方法与 `close` 方法。
我们先试试读取 /usr/bin 目录。
~~~
dir = Dir.open("/usr/bin")
while name = dir.read
p name
end
dir.close
~~~
我们也可以像下面那样用 `Dir#each` 方法替换 `while` 语句部分。
~~~
dir = Dir.open("/usr/bin")
dir.each do |name|
p name
end
dir.close
~~~
和 `File.open` 同样,对 `Dir.open` 使用块后也可以省略 `close` 方法的调用。这时程序会将生成的 `Dir` 对象传给块变量。
~~~
Dir.open("/usr/bin") do |dir|
dir.each do |name|
p name
end
end
~~~
程序会输出以下内容。
~~~
"."
".."
"gnomevfs-copy"
"updmap"
"signver"
"bluetooth-sendto"
┊
~~~
-
**`dir.read`**
与 `File` 类一样,`Dir` 类也有 `read` 方法。
执行 `Dir#read` 后,程序会遍历读取最先打开的目录下的内容。这里读取的内容可分为以下 4 类:
-
**表示当前目录的 .**
-
**表示上级目录的 ..**
-
**其他目录名**
-
**文件名**
请注意 /usr/bin 与 /usr/bin/. 表示同一个目录。
代码清单 18.1 中的程序会操作指定目录下的所有路径。命令行参数 `ARGV[0]` 的路径为目录时,会对该目录下的文件进行递归处理,除此以外(文件)的情况下则调用 `process_file` 方法。`traverse` 方法会输出指定目录下的所有文件名,执行结果只显示在控制台中。
注释中带 ※ 的代码表示忽略当前目录和上级目录,不这么做的话,就会陷入无限循环中,不断地重复处理同一个目录。
**代码清单 18.1 traverse.rb**
~~~
def traverse(path)
if File.directory?(path) # 如果是目录
dir = Dir.open(path)
while name = dir.read
next if name == "." # ※
next if name == ".." # ※
traverse(path + "/" + name)
end
dir.close
else
process_file(path) # 处理文件
end
end
def process_file(path)
puts path # 输出结果
end
traverse(ARGV[0])
~~~
-
**`Dir.glob`**
使用 `Dir.glob` 方法后,就可以像 shell 那样使用 `*` 或者 `?` 等通配符(wildcard character)来取得文件名。`Dir.glob` 方法会将匹配到的文件名(目录名)以数组的形式返回。
下面我们列举一些常用的匹配例子。
-
**获取当前目录中所有的文件名。(无法获取 Unix 中以 "." 开始的隐藏文件名)**
~~~
Dir.glob("*")
~~~
-
**获取当前目录中所有的隐藏文件名**
~~~
Dir.glob(".*")
~~~
-
**获取当前目录中扩展名为 .html 或者 .htm 的文件名。可通过数组指定多个模式。**
~~~
Dir.glob(["*.html", "*.htm"])
~~~
-
**模式中若没有空白,则用 %w(...) 生成字符串数组会使程序更加易懂。**
~~~
Dir.glob(%w(*.html *.htm))
~~~
-
**获取子目录下扩展名为 .html 或者 .htm 的文件名。**
~~~
Dir.glob(["*/*.html", "*/*.htm"])
~~~
-
**获取文件名为 foo.c、foo.h、foo.o 的文件。**
~~~
Dir.glob("foo.[cho]")
~~~
-
**获取当前目录及其子目录中所有的文件名,递归查找目录。**
~~~
Dir.glob("**/*")
~~~
-
**获取目录 foo 及其子目录中所有扩展名为 .html 的文件名,递归查找目录。**
~~~
Dir.glob("foo/**/*.html")
~~~
可以像下面那样用 `Dir.glob` 方法改写代码清单 18.1 的 `traverse` 方法。
**代码清单 18.2 traverse_by_glob.rb**
~~~
def traverse(path)
Dir.glob(["#{path}/**/*", "#{path}/**/.*"]).each do |name|
unless File.directory?(name)
process_file(name)
end
end
end
~~~
### **18.2.2 目录的创建与删除**
-
**`Dir.mkdir`(*path*)**
创建新目录用 `Dir.mkdir` 方法。
~~~
Dir.mkdir("temp")
~~~
-
**`Dir.rmdir`(*path*)**
删除目录用 `Dir.rmdir` 方法。要删除的目录必须为空目录。
~~~
Dir.rmdir("temp")
~~~
### **18.3 文件与目录的属性**
文件与目录都有所有者、最后更新时间等属性。接下来我们就来看看如何引用和更改这些属性。
-
**`File.stat`(*path*)**
通过 `File.stat` 方法,我们可以获取文件、目录的属性。`File.stat` 方法返回的是 `File::Stat` 类的实例。`File::Stat` 类的实例方法如表 18.1 所示。
**表 18.1 FIle::Stat 类的实例方法**
| 方法 | 返回值的含义 |
|-----|-----|
| `dev` | 文件系统的编号 |
| `ino` | i-node 编号 |
| `mode` | 文件的属性 |
| `nlink` | 链接数 |
| `uid` | 文件所有者的用户 ID |
| `gid` | 文件所属组的组 ID |
| `rdev` | 文件系统的驱动器种类 |
| `size` | 文件大小 |
| `blksize` | 文件系统的块大小 |
| `blocks` | 文件占用的块数量 |
| `atime` | 文件的最后访问时间 |
| `mtime` | 文件的最后修改时间 |
| `ctime` | 文件状态的最后更改时间 |
其中,除 `atime` 方法、`mtime` 方法、`ctime` 方法返回 `Time` 对象外,其他方法都返回整数值。
通过 `uid` 方法与 `gid` 方法获取对应的用户 ID 与组 ID 时,需要用到 `Etc` 模块。我们可以通过 `require 'etc'` 来引用 `Etc` 模块。
> **备注** mswin32 版的 Ruby 不能使用 `Etc` 模块。
下面是显示文件 /usr/local/bin/ruby 的用户名与组名的程序:
~~~
require 'etc'
st = File.stat("/usr/local/bin/ruby")
pw = Etc.getpwuid(st.uid)
p pw.name #=> "root"
gr = Etc.getgrgid(st.gid)
p gr.name #=> "wheel"
~~~
-
**`File.ctime`(*path*)
`File.mtime`(*path*)
`File.atime`(*path*)**
这三个方法的执行结果与实例方法 `File::Stat#ctime`、`File::Stat#mtime`、`File::Stat#atime` 是一样的。需要同时使用其中的两个以上的方法时,选择实例方法会更加有效率。
-
**`File.utime`(*atime, mtime, path*)**
改变文件属性中的最后访问时间 *atime*、最后修改时间 *mtime*。时间可以用整数值或者 `Time` 对象指定。另外,同时还可以指定多个路径。下面是修改文件 foo 的最后访问时间以及最后修改时间的程序。通过 `Time.now` 方法创建表示当前时间的 `Time` 对象,然后将时间设为当前时间减去 100 秒。
~~~
filename = "foo"
File.open(filename, "w").close # 创建文件后关闭
st = File.stat(filename)
p st.ctime #=> 2013-03-30 04:20:01 +0900
p st.mtime #=> 2013-03-30 04:20:01 +0900
p st.atime #=> 2013-03-30 04:20:01 +0900
File.utime(Time.now-100, Time.now-100, filename)
st = File.stat(filename)
p st.ctime #=> 2013-03-30 04:20:01 +0900
p st.mtime #=> 2013-03-30 04:18:21 +0900
p st.atime #=> 2013-03-30 04:18:21 +0900
~~~
-
**`File.chmod`(*mode, path*)**
修改文件 *path* 的访问权限(permission)。*mode* 的值为整数值,表示新的访问权限值。同时还能指定多个路径。
> **备注** 在 Windows 中只有文件的所有者才有写入的权限。执行权限则是由扩展名(.bat、.exe 等)决定的。
访问权限是用于表示是否可以进行执行、读取、写入操作的 3 位数(图 18.2)。访问权限又细分为所有者、所属组、其他三种权限,因此完整的访问权限用 9 位数表示。
![{%}](https://box.kancloud.cn/2015-10-26_562e020446ff1.png)
**图 18.2 访问权限值的含义**
例如,设定“所有者为读写权限,其他用户为只读权限”时的权限位(permission bit)为 110100100。由于 8 进制刚好是用 1 个数字表示 3 位,因此一般会将权限位用 8 进制表示。刚才的 110100100 用 8 进制表示则为 0644。
以下程序是将文件 test.txt 的访问权限设为 0755(所有者拥有所有权限,其他用户为只读权限):
~~~
File.chmod(0755, "test.txt")
~~~
像对现在的访问权限追加执行权限这样追加特定运算时,需要对从 `File.stat` 方法得到的访问权限位进行追加位的位运算,然后再用计算后的新值重新设定。使用按位或运算追加权限位。
~~~
rb_file = "test.rb"
st = File.stat(rb_file)
File.chmod(st.mode | 0111, rb_file) # 追加执行权限
~~~
-
**`File.chown`(*owner, group, path*)**
改变文件 *path* 的所有者。*owner* 表示新的所有者的用户 ID,*group* 表示新的所属组 ID。同时也可以指定多个路径。执行这个命令需要有管理员的权限。
> **备注** Windows 中虽然也提供了这个方法,但调用时什么都不会发生。
### **FileTest 模块**
`FileTest` 模块中的方法用于检查文件的属性(表 18.2)。该模块可以在 include 后使用,也可以直接作为模块函数使用。其中的方法也可以作为 `File` 类的类方法使用。
**表 18.2 FileTest 模块的方法**
<table border="1" data-line-num="369 370 371 372 373 374 375 376 377 378 379 380 381 382" width="90%"><thead><tr><th> <p class="表头单元格">方法</p> </th> <th> <p class="表头单元格">返回值</p> </th> </tr></thead><tbody><tr><td> <p class="表格单元格"><code>exist?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 若存在则返回 <code>true</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>file?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 若是文件则返回 <code>true</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>directory?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 若是目录则返回 <code>true</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>owned?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 的所有者与执行用户一样则返回 <code>true</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>grpowned?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 的所属组与执行用户的所属组一样则返回 <code>true</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>readable?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 可读取则返回 <code>true</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>writable?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 可写则返回 <code>true</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>executable?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 可执行则返回 <code>true</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>size</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格">返回 <em>path</em> 的大小</p> </td> </tr><tr><td> <p class="表格单元格"><code>size?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 的大小比 0 大则返回 <code>true</code> ,大小为 0 或者文件不存在则返回 <code>nil</code></p> </td> </tr><tr><td> <p class="表格单元格"><code>zero?</code>(<em>path</em>)</p> </td> <td> <p class="表格单元格"><em>path</em> 的大小为 0 则返回 <code>true</code></p> </td> </tr></tbody></table>
### **18.4 文件名的操作**
操作文件时,我们常常需要操作文件名。Ruby 为我们提供了从路径名中获取目录名、文件名的方法、以及相反的由目录名和文件名生成路径名的方法。
-
**`File.basename`(*path*[*, suffix*])**
返回路径 *path* 中最后一个 `"/"` 以后的部分。如果指定了扩展名 *suffix*,则会去除返回值中扩展名的部分。在从路径中获取文件名的时候使用本方法。
~~~
p File.basename("/usr/local/bin/ruby") #=> "ruby"
p File.basename("src/ruby/file.c", ".c") #=> "file"
p File.basename("file.c") #=> "file"
~~~
-
**`File.dirname`**(*path*)
返回路径 *path* 中最后一个 `"/"` 之前的内容。路径不包含 `"/"` 时则返回 `"."`。在从路径中获取目录名的时候使用本方法。
~~~
p File.dirname("/usr/local/bin/ruby") #=> "/usr/local/bin"
p File.dirname("ruby") #=> "."
p File.dirname("/") #=> "/"
~~~
-
**`File.extname`**(*path*)
返回路径 *path* 中 `basename` 方法返回结果中的扩展名。没有扩展名或者以 `"."` 开头的文件名时则返回空字符串。
~~~
p File.extname("helloruby.rb") #=> ".rb"
p File.extname("ruby-2.0.0-p0.tar.gz") #=> ".gz"
p File.extname("img/foo.png") #=> ".png"
p File.extname("/usr/local/bin/ruby") #=> ""
p File.extname("~/.zshrc") #=> ""
p File.extname("/etc/init.d/ssh") #=> ""
~~~
-
**`File.split`**(*path*)
将路径 *path* 分割为目录名与文件名两部分,并以数组形式返回。在知道返回值的数量时,使用多重赋值会方便得多。
~~~
p File.split("/usr/local/bin/ruby")
#=> ["/usr/local/bin", "ruby"]
p File.split("ruby") #=> [".", "ruby"]
p File.split("/") #=> ["/", ""]
dir, base = File.split("/usr/local/bin/ruby")
p dir #=> "/usr/local/bin"
p base #=> "ruby"
~~~
-
**`File.join`(*name1*[*, name2, …*])**
用 `File::SEPARATOR` 连接参数指定的字符串。`File::SEPARATOR` 的默认设值为 `"/"` 。
~~~
p File.join("/usr/bin", "ruby") #=> "/usr/bin/ruby"
p File.join(".", "ruby") #=> "./ruby"
~~~
-
**`File.expand_path`(*path*[*, default_dir*])**
根据目录名 *default_dir*,将相对路径 *path* 转换为绝对路径。不指定 *default_dir* 时,则根据当前目录转换。
~~~
p Dir.pwd #=> "/usr/local"
p File.expand_path("bin") #=> "/usr/local/bin"
p File.expand_path("../bin") #=> "/usr/bin"
p File.expand_path("bin", "/usr") #=> "/usr/bin"
p File.expand_path("../etc", "/usr") #=> "/etc"
~~~
在 Unix 中,可以用 `~` 用户名的形式获取用户的主目录(home directory)。`~/` 表示当前用户的主目录。
~~~
p File.expand_path("~gotoyuzo/bin") #=> "/home/gotoyuzo/bin"
p File.expand_path("~takahashim/bin") #=> "/home/takahashim/bin"
p File.expand_path("~/bin") #=> "/home/gotoyuzo/bin"
~~~
### **18.5 与操作文件相关的库**
接下来我们来介绍一下 Ruby 默认提供的与操作文件相关的库。预定义的 `File` 类与 `Dir` 类只提供了 OS 中 Ruby 可以操作的最基本的功能。为了提高写程序的效率,有必要掌握本节中所介绍的库。
### **18.5.1 find 库**
`find` 库中的 `find` 模块被用于对指定的目录下的目录或文件做递归处理。
-
**`Find.find`(*dir*){|*path*| …}
`Find.prune`**
`Find.find` 方法会将目录 *dir* 下的所有文件路径逐个传给路径 *path*。
使用 `Find.find` 方法时,调用 `Find.prune` 方法后,程序会跳过当前查找目录下的所有路径(只是使用 `next` 时,则只会跳过当前目录,子目录的内容还是会继续查找)。
代码清单 18.3 是一个显示命令行参数指定目录下内容的脚本。`listdir` 方法会显示参数 `top` 路径下所有的目录名。将不需要查找的目录设定到 `IGNORES` 后,就可以通过 `Find.prune` 方法忽略那些目录下的内容的处理。
**代码清单 18.3 listdir.rb**
~~~
require 'find'
IGNORES = [ /^\./, /^CVS$/, /^RCS$/ ]
def listdir(top)
Find.find(top) do |path|
if FileTest.directory?(path) # 如果path 是目录
dir, base = File.split(path)
IGNORES.each do |re|
if re =~ base # 需要忽略的目录
Find.prune # 忽略该目录下的内容的查找
end
end
puts path # 输出结果
end
end
end
listdir(ARGV[0])
~~~
### **18.5.2 tempfile 库**
`tempfile` 库用于管理临时文件。
在处理大量数据的程序中,有时候会将一部分正在处理的数据写入到临时文件。这些文件一般在程序执行完毕后就不再需要,因此必须删除,但为了能够确实删除文件,就必须记住每个临时文件的名称。此外,有时候程序还会同时处理多个文件,或者同时执行多个程序,考虑到这些情况,临时文件还不能使用相同的名称,而这就形成了一个非常麻烦的问题。
`tempfile` 库中的 `Tempfile` 类就是为了解决上述问题而诞生的。
-
**`Tempfile.new`(*basename*[*, tempdir*])**
创建临时文件。实际生成的文件名的格式为“*basename*+ 进程 ID+ 流水号”。因此,即使使用同样的 *basename*,每次调用 `new` 方法生成的临时文件也都是不一样的。如果不指定目录名 *tempdir*,则会按照顺序查找 `ENV["TMPDIR"]`、`ENV["TMP"]`、`ENV["TEMP"]`、`/tmp`,并把最先找到的目录作为临时目录使用。
-
**`tempfile.close`(*real*)**
关闭临时文件。*real* 为 `ture` 时则马上删除临时文件。即使没有明确指定删除,`Tempfile` 对象也会在 GC(详情请参考本章最后的专栏) 的时候并一并删除。*real* 的默认值为 `false`。
-
**`tempfile.open`**
再次打开 `close` 方法关闭的临时文件。
-
**`tempfile.path`**
返回临时文件的路径。
### **18.5.3 fileutils 库**
在之前的内容中,我们已经接触过了 `fileutils` 库的 `FileUtils.cp`、`FileUtils.mv` 方法。通过 `require` 引用 `fileutils` 库后,程序就可以使用 `FileUtils` 模块中提供的各种方便的方法来操作文件。
-
**`FileUtils.cp`(*from, to*)**
把文件从 *from* 拷贝到 *to*。*to* 为目录时,则在 *to* 下面生成与 *from* 同名的文件。此外,也可以将 *from* 作为数组来一次性拷贝多个文件,这时 *to* 必须指定为目录。
-
**`FileUtils.cp_r`(*from, to*)**
功能与 `FileUtils.cp` 几乎一模一样,不同点在于 *from* 为目录时,则会进行递归拷贝。
-
**`FileUtils.mv`(*from, to*)**
把文件(或者目录)*from* 移动到 *to*。*to* 为目录时,则将文件作为与 *from* 同名的文件移动到 *to* 目录下。也可以将 *from* 作为数组来一次性移动多个文件,这时 *to* 必须指定为目录。
-
**`FileUtils.rm`(*path*)
`FileUtils.rm_f`(*path*)**
删除 *path*。*path* 只能为文件。也可以将 *path* 作为数组来一次性删除多个文件。`FileUtils.rm` 方法在执行删除处理的过程中,若发生异常则中断处理,而 `FileUtils.rm\_f` 方法则会忽略错误,继续执行。
-
**`FileUtils.rm_r`(*path*)
`FileUtils.rm_rf`(*path*)**
删除 *path*。*path* 为目录时,则进行递归删除。此外,也可以将 *path* 作为数组来一次性删除多个文件(或者目录)。`FileUtils.rm_r` 方法在执行处理的过程中,若发生异常则中断处理,而 `FileUtils.rm_rf` 方法则会忽略错误,继续执行。
-
**`FileUtils.compare`(*from, to*)**
比较 *from* 与 *to* 的内容,相同则返回 `true`,否则则返回 `false`。
-
**`FileUtils.install`(*from, to*[*, option*])**
把文件从 *from* 拷贝到 *to*。如果 *to* 已经存在,且与 *from* 内容一致,则不会拷贝。*option* 参数用于指定目标文件的访问权限,如下所示。
~~~
FileUtils.install(from, to, :mode => 0755)
~~~
-
**`FileUtils.mkdir_p`(*path*)**
使用 `Dir.mkdir` 方法创建 `"foo/bar/baz"` 这样的目录时,需要像下面那样按顺序逐个创建上层目录。
~~~
Dir.mkdir("foo")
Dir.mkdir("foo/bar")
Dir.mkdir("foo/bar/baz")
~~~
而如果使用 `FileUtils.mkdir\_p` 方法,则只需调用一次就可以自动创建各层的目录。此外,也可以将 *path* 作为数组来一次性创建多个目录。
~~~
FileUtils.mkdir_p("foo/bar/baz")
~~~
> **专栏**
> **关于 GC**
> 在第 3 部中我们介绍了各种类型的对象,而在程序中生成这些对象(一部分除外)时都会消耗内存空间。例如数组、字符串等,如果长度变大了,那么它们需要的内存空间也会随之变大。程序为了能正常运行不可避免地要创建对象,但是计算机的内存空间却不是可以无限使用的,因此就必须释放已经不需要的对象所占用的内存空间。
> 下面的写法在本书中已经出现过多次,在这种情况下,变量 `line` 引用的字符串,在下一次读取时就不能再被引用了。
~~~
io.each_line do |line|
print(line)
end
~~~
> 还有,在方法执行完毕后,在方法中临时生成的对象也不再需要了。
~~~
def hello(name)
msg = "Hello, #{name}" #=> 创建新的字符串对象
puts(msg)
end
~~~
> 但是,内存空间释放并不是大部分程序主要关心的功能,而且忘记释放内存,或者错把正在用的对象释放等,都很可能引起难缠的程序漏洞(bug)。因此,在 Ruby(Java、Perl、Lisp 等语言也都具备这样的功能)中,解析器(interpreter)会在适当的时机,释放已经没有被任何地方引用的对象所占的资源。这样的功能,我们称之为 Garbage Collection(垃圾回收的意思),简称 GC。
> 有了 GC 后,我们就无需再为内存管理而烦恼了。GC 是支撑 Ruby 的宗旨——快乐编程的重要功能之一。
### **练习题**
1. 变量 `$:` 以数组的形式保存着 Ruby 中可用的库所在的目录。定义 `print_libraries` 方法,利用变量 `$:`,按名称顺序输出 Ruby 中可用的库。
2. 定义 `du` 方法,功能类似于 Unix 命令 du,递归输出文件及目录中的数据大小。
本方法只有一个参数。
**`du`( 目录名)**
输出指定目录下的文件大小(字节数)以及目录大小。目录大小为该目录下所有文件大小的总和。
> 参考答案:请到图灵社区本书的“随书下载”处下载([http://www.ituring.com.cn/book/1237](http://www.ituring.com.cn/book/1237))。
- 推荐序
- 译者序
- 前言
- 本书的读者对象
- 第 1 部分 Ruby 初体验
- 第 1 章 Ruby 初探
- 第 2 章 便利的对象
- 第 3 章 创建命令
- 第 2 部分 Ruby 的基础
- 第 4 章 对象、变量和常量
- 第 5 章 条件判断
- 第 6 章 循环
- 第 7 章 方法
- 第 8 章 类和模块
- 第 9 章 运算符
- 第 10 章 错误处理与异常
- 第 11 章 块
- 第 3 部分 Ruby 的类
- 第 12 章 数值类
- 第 13 章 数组类
- 第 14 章 字符串类
- 第 15 章 散列类
- 第 16 章 正则表达式类
- 第 17 章 IO 类
- 第 18 章 File 类与 Dir 类
- 第 19 章 Encoding 类
- 第 20 章 Time 类与 Date 类
- 第 21 章 Proc 类
- 第 4 部分 动手制作工具
- 第 22 章 文本处理
- 第 23 章 检索邮政编码
- 附录
- 附录 A Ruby 运行环境的构建
- 附录 B Ruby 参考集
- 后记
- 谢辞