# **第 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),根目錄只用 / 表示。

**圖 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 位數表示。

**圖 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 參考集
- 后記
- 謝辭