# **第 17 章 IO 類**
到目前為止出現的程序中有一些是關于處理文件中的數據的。`IO` 類的主要作用就是讓程序與外部進行數據的輸入(input)/ 輸出(output)操作。在本章中,我們將會討論以下內容。
-
**輸入/ 輸出的種類**
介紹 `IO` 類支持的輸入 / 輸出對象。
-
**基本的輸入/ 輸出操作**
介紹 `IO` 類的基本操作,以行為單位或者以大小為單位進行讀寫操作。
-
**文件指針、二進制模式 / 文本模式、緩沖處理**
深入介紹一些與輸入 / 輸出有關的細節。二進制模式與文本模式是 Windows 特有的規范,若不注意常常會出現問題。
-
**與命令進行交互**
介紹與外部命令進行數據交換的方法
### **17.1 輸入 / 輸出的種類**
我們首先來了解一下輸入 / 輸出的對象到底是什么。
### **17.1.1 標準輸入 / 輸出**
程序在啟動后會預先分配 3 個 `IO` 對象。
-
**標準輸入**
標準輸入可以獲取從鍵盤輸入的內容。通過預定義常量 `STDIN` 可調用操作標準輸入的 `IO` 對象。另外,用全局變量 `$stdin` 也可以引用標準輸入的 `IO` 對象。不指定接收者的 `gets` 方法等都會默認從標準輸入中獲取數據。
-
**標準輸出**
向標準輸出寫入的數據會顯示在屏幕中。通過預定義常量 `STDOUT` 可調用操作標準輸出的 `IO` 對象。另外,用全局變量 `$stdout` 也可以引用標準輸出的 `IO` 對象。不指定接收者的 `puts`、`print`、`printf` 等方法會默認將數據寫入到標準輸出。
-
**標準錯誤輸出**
向標準錯誤輸出寫入的數據會顯示在屏幕中。通過預定義常量 `STDERR` 可調用操作標準錯誤輸出的 `IO` 對象。另外,用全局變量 `$stderr` 也可以引用標準錯誤輸出的 `IO` 對象。
標準錯誤輸出原本是用于輸出錯誤信息的,但實際上,除輸出警告或者錯誤之外,在希望與程序正常輸出的信息做出區別時也可以使用它(代碼清單 17.1)。
**代碼清單 17.1 out.rb**
~~~
$stdout.print "Output to $stdout.\n" # 標準輸出
$stderr.print "Output to $stderr.\n" # 標準錯誤輸出
~~~
> **執行示例**
~~~
> ruby out.rb
Output to $stdout.
Output to $stderr.
~~~
將輸出結果重定向到文件時,標準輸出的內容會被寫入到文件,只有標準錯誤輸出的內容被 輸出到屏幕中。
> **執行示例**
~~~
> ruby out.rb > log.txt
Output to $stderr.
~~~
> **備注** 在執行程序時,在命令后加上 `>` 文件名,就可以將程序執行時的輸出結果保存到文件中。我們把這個控制臺的功能稱為“重定向”。通過這個功能,不僅 ruby 命令,程序的輸出內容也都可以保存在文件中。
根據需要靈活使用標準輸出與標準錯誤輸出,可以使我們很方便地分開獲取正常信息與錯誤信息。
> **備注** ruby 命令的錯誤信息也會被輸出到標準錯誤輸出。
通常標準輸入、標準輸出、標準錯誤輸出都是與控制臺關聯的。但是將命令的輸出重定向到文件,或者使用管道(pipe)將結果傳遞給其他程序時則與控制臺沒有關系。根據實際的使用情況,程序的輸入 / 輸出狀態也各異。`IO` 對象是否與控制臺關聯,我們可以通過 `tty?` 方法判斷。
代碼清單 17.2 是一個檢查標準輸入是否為屏幕的例子。
**代碼清單 17.2 tty.rb**
~~~
if $stdin.tty?
print "Stdin is a TTY.\n"
else
print "Stdin is not a TTY.\n"
end
~~~
下面我們用不同的方式調用這個程序,看看有何不同。首先是普通調用。
> **執行示例**
~~~
> ruby tty.rb
Stdin is a TTY.
~~~
將命令的輸出結果傳給管道,或者通過文件輸入內容時,程序的結果會不一樣。
> **執行示例**
~~~
> echo | ruby tty.rb
Stdin is not a TTY.
> ruby tty.rb < data.txt
Stdin is not a TTY.
~~~
> **備注** TTY 是 TeleTYpe 的縮寫。
### **17.1.2 文件輸入 / 輸出**
通過 `IO` 類的子類 `File` 類可以進行文件的輸入 / 輸出處理。`File` 類中封裝了文件刪除、文件屬性變更等文件專用的功能,而一些基本的輸入 / 輸出處理則使用繼承自 IO 類的方法。
-
***io*= `File.open`(*file, mode*)
*io* = `open`(*file, mode*)**
通過 `File.open` 方法或 `open` 方法打開文件并獲取新的 `IO` 對象。
模式(mode)會指定以何種目的打開文件(表 17.1)。缺省模式為只讀模式(`"r"`)。在 Windows 環境下,在各模式后加上 `b`、通過 `"rb"`、`"rb+"` 等這樣的形式即可表示二進制模式(后述)。
**表 17.1 模式**
| 模式 | 意義 |
|-----|-----|
| `r` | 用只讀模式打開文件。 |
| `r+` | 用讀寫模式打開文件。 |
| `w` | 用只寫模式打開文件。文件不存在則創建新的文件;文件已存在則清空文件,即將文件大小設置為0。 |
| `w+` | 讀寫模式,其余同 `"w"` 。 |
| `a` | 用追加模式打開文件。文件不存在則創建新的文件。 |
| `a+` | 用讀取/ 追加模式打開文件。文件不存在則創建新的文件。 |
-
***io*.`close`**
使用 `close` 方法關閉已打開的文件。
1 個程序中同時打開文件的數量是有限制的,因此使用完的文件應該盡快關閉。如果打開多個文件而不進行關閉操作,程序就很可能會在使用 `open` 方法時突然產生異常。
`File.open` 方法如果使用塊,則文件會在使用完畢后自動關閉。這種情況下,`IO` 對象會被作為塊變量傳遞給塊。塊執行完畢后,塊變量引用的 `IO` 對象也會自動關閉。這種寫法會使輸入 / 輸出的操作范圍更加清晰。
~~~
File.open("foo.txt") do |io|
while line = io.gets
┊
end
end
~~~
-
***io*.`close?`**
用 `close?` 方法可以檢查 `IO` 對象是否關閉了。
~~~
io = File.open("foo.txt")
io.close
p io.closed? #=> true
~~~
-
**`File.read`(*file*)**
使用類方法 `read` 可以一次性讀取文件 `file` 的內容。
~~~
data = File.read("foo.txt")
~~~
> **備注** 在 Windows 中不能使用 `File.read` 方法讀取像圖像數據等二進制數據。`File.read` 方法使用文本模式打開文件時,會對換行符等進行轉換,因此無法得到正確的結果。詳細內容請參考 17.4 節。
### **17.2 基本的輸入 / 輸出操作**
輸入 / 輸出操作的數據為字符串,也就是所謂的 `String` 對象。執行輸入操作后,會從頭到尾按順序讀取數據,執行輸出操作后,則會按寫入順序不斷追加數據。
### **17.2.1 輸入操作**
-
***io*.`gets`(*rs*)
*io*.`each`(*rs*)
*io*.`each_line`(*rs*)
*io*.`readlines`(*rs*)**
從 `IO` 類的對象 *io* 中讀取一行數據。用參數 *rs* 的字符串分行。省略 *rs* 時則用預定義變量 `$/`(默認值為 `"\n"`)。
這些方法返回的字符串中包含行末尾的換行符。用 `chmop!` 方法可以很方便地刪除字符串末尾的換行符。
輸入完畢后再嘗試獲取數據時,`gets` 方法會返回 `nil`。另外,我們還可以使用 `eof?` 方法檢查輸入是否已經完畢。
~~~
while line = io.gets
line.chomp!
┊ # 對line 進行的操作
end
p io.eof? #=> true
~~~
`while` 條件表達式中同時進行了變量賦值與條件判斷的操作。將 `gets` 方法的返回值復制給 `line`,并將該值作為 `while` 語句的條件來判斷。上面是 `gets` 方法的經典用法,大家應該盡快掌握這種寫法。
用 `each_line` 方法也可以實現同樣的效果。
~~~
io.each_line do |line|
line.chomp!
┊ # 對line 進行的操作
end
~~~
另外,用 `readlines` 方法可以一次性地讀取所有數據,并返回將每行數據作為元素封裝的數組。
~~~
ary = io.readlines
ary.each_line do |line|
line.chomp!
┊ # 對line 進行的操作
end
~~~
> **備注** `gets` 方法與 `puts` 方法,分別是“get string”、“put string”的意思。
-
***io*.`lineno`
*io*.`lineno`=(*number*)**
使用 `gets` 方法、`each_line` 方法逐行讀取數據時,會自動記錄讀取的行數。這個行數可以通過 `lineno` 方法取得。此外,通過 `lineno=` 方法也可以改變這個值,但值的改變并不會對文件指針(后述)有影響。
在下面的例子中,逐行讀取標準輸入的數據,并在行首添加行編號。
~~~
$stdin.each_line do |line|
printf("%3d %s", $stdin.lineno, line)
end
~~~
-
***io*.`each_char`**
逐個字符地讀取 *io* 中的數據并執行塊。將得到的字符(`String` 對象)作為塊變量傳遞。
~~~
io.each_char do |ch|
┊ # 對line 進行的操作
end
~~~
-
***io*.`each_byte`**
逐個字節地讀取 *io* 中的數據并啟動塊。將得到的字節所對應的 ASCII 碼以整數值的形式傳遞給塊變量。
-
***io*.`getc`**
只讀取 *io* 中的一個字符。根據文件編碼的不同,有時一個字符會由多個字節組成,但這個方法只會讀取一個字符,然后返回其字符串對象。數據全部讀取完后再讀取時會返回 `nil`。
~~~
while ch = io.getc
┊ # 對line 進行的操作
end
~~~
-
***io*.`ungetc`(*ch*)**
將參數 *ch* 指定的字符退回到 *io* 的輸入緩沖中。
~~~
# hello.txt 中的內容為“Hello, Ruby.\n”
File.open("hello.txt") do |io|
p io.getc #=> "H"
io.ungetc(72)
p io.gets #=> "Hello, Ruby.\n"
end
~~~
指定一個字符大小的字符串對象。對可退回的字符數沒有限制。
-
***io*.`getbyte`**
只讀取 *io* 中的一個字節,返回得到的字節轉換為 ASCII 碼后的整數對象。數據全部讀取完后再讀取時會返回 `nil`。
-
***io*.`ungetbyte`(*byte*)**
將參數 *byte* 指定的一個字節退回到輸入緩沖中。參數為整數時,將該整數除以 256 后的余數作為 ASCII 碼字符返回一個字節;參數為字符串時,只返回字符串的第一個字節。
-
***io*.`read`(*size*)**
讀取參數 *size* 指定的大小的數據。不指定大小時,則一次性讀取全部數據并返回。
~~~
# hello.txt 中的內容為"Hello, Ruby.\n"
File.open("hello.txt") do |io|
p io.read(5) #=> "Hello"
p io.read #=> ",Ruby.\n"
end
~~~
### **17.2.2 輸出操作**
-
***io*.`puts`(*str0, str1,* …)**
對字符串末尾添加換行符后輸出。指定多個參數時,會分別添加換行符。如果參數為 `Sting` 類以外的對象,則會調用 `to_s` 方法,將其轉換為字符串后再輸出。
**代碼清單 17.3 stdout_put.rb**
~~~
$stdout.puts "foo", "bar", "baz"
~~~
> **執行示例**
~~~
> ruby stdout_put.rb
foo
bar
baz
~~~
-
***io*.`putc`(*ch*)**
輸出參數 *ch* 指定的字符編碼所對應的字符。參數為字符串時輸出首字符。
**代碼清單 17.4 stdout_putc.rb**
~~~
$stdout.putc(82) # 82 是R 的ASCII 碼
$stdout.putc("Ruby")
$stdout.putc("\n")
~~~
> **執行示例**
~~~
> ruby stdout_putc.rb
RR
~~~
-
***io*.`print`(*str0, str1,* …)**
輸出參數指定的字符串。參數可指定多個字符串。參數為 `String` 以外的對象時會自動將其轉換為字符串。
-
***io*.`printf`(*fmt, arg0, arg1,* …)**
按照指定的格式輸出字符串。格式 *fmt* 的用法與 `printf` 方法一樣,請參考第 192 頁專欄《printf 方法與 sprintf 方法》。
-
***io*.`write`(*str*)**
輸出參數 *str* 指定的字符串。參數為 `String` 以外的對象時會自動將其轉換為字符串。方法返回值為輸出的字節數。
~~~
size = $stdout.write("Hello.\n") #=> Hello.
p size #=> 6
~~~
-
***io*<<*str***
輸出參數 *str* 指定的字符串。`<<` 會返回接收者本身,因此可以像下面這樣寫:
~~~
io << "foo" << "bar" << "baz"
~~~
### **17.3 文件指針**
一般情況下,我們會以行為單位處理文本數據。由于只有當讀取到換行符時才能知道行的長度,因此,如要讀取第 100 行的數據,就意味著要將這 100 行的數據全部讀取。另外,如果我們修改了數據,行的長度也會隨之變更,這樣一來,文件中后面的數據就都要做出修改。
為了提高讀取效率,可以將文件分成固定長度的文件塊,來直接訪問某個位置的數據(雖然據此可以訪問任意位置的數據,但卻不能處理超過指定長度的數據)。
我們用文件指針(file pointer)或者當前文件偏移量(current file offset)來表示 `IO` 對象指向的文件的位置。每當讀寫文件時,文件指針都會自動移動,而我們也可以使文件指針指向任意位置來讀寫數據。
-
***io*.`pos`
*io*.`pos`=(*position*)**
通過 `pos` 方法可以獲得文件指針現在的位置。改變文件指針的位置用 `pos=` 方法。
~~~
# hello.txt 中的內容為"Hello, Ruby.\n"
File.open("hello.txt") do |io|
p io.read(5) #=> "Hello"
p io.pos #=> 5
io.pos = 0
p io.gets #=> "Hello, Ruby.\n"
end
~~~
-
***io*.`seek`(*offset, whence*)**
移動文件指針的方法。參數 *offset* 為用于指定位置的整數,參數 *whence* 用于指定 *offset* 如何移動(表 17.2)。
**表 17.2 whence 中指定的值**
| whence | 意義 |
|-----|-----|
| `IO::SEEK_SET` | 將文件指針移動到 *offset* 指定的位置 |
| `IO::SEEK_CUR` | 將 *offset* 視為相對于當前位置的偏移位置來移動文件指針 |
| `IO::SEEK_END` | 將 *offset* 指定為相對于文件末尾的偏移位置 |
-
***io*.`rewind`**
將文件指針返回到文件的開頭。`lineno` 方法返回的行編號為 0。
~~~
# hello.txt 中的內容為"Hello, Ruby.\n"
File.open("hello.txt")do |io|
p io.gets #=> "Hello,Ruby.\n"
io.rewind
p io.gets #=> "Hello, Ruby.\n"
end
~~~
-
***io*.`truncate`(*size*)**
按照參數 *size* 指定的大小截斷文件。
~~~
io.truncate(0) # 將文件大小置為0
io.truncate(io.pos) # 刪除當前文件指針以后的數據
~~~
### **17.4 二進制模式與文本模式**
正如我們在第 14 章的專欄《關于換行符》中介紹的那樣,不同平臺下的換行符也不同。
雖然各個平臺的換行符不一樣,但為了保證程序的兼容性,會將字符串中的 `"\n"` 轉換為當前 OS 的換行符并輸出。此外,在讀取的時候也會將實際的換行符轉換為 `"\n"`。
圖 17.1 為在 Windows 中轉換換行符的情形。

**圖 17.1 Windows 環境中字符 `"\n"` 的轉換**
當需要確定文件大小進行輸入 / 輸出處理時,或者直接使用從其他平臺拷貝的文件時,如果進行換行符轉換,就有可能會引發問題。
為了解決上述那樣的問題,Ruby 中還提供了不進行換行符轉換的方法。換行符處理的前提是以行為單位做輸入 / 輸出處理,需要轉換時稱為文本模式,反之不需要轉換時則稱為二進制模式。
-
***io*.`binmode`**
新的 `IO` 對象默認是文本模式,使用 `binmode` 方法可將其變更為二進制模式。
~~~
File.open("foo.txt", "w") do |io|
io.binmode
io.write "Hello, world.\n"
end
~~~
這樣就可以既不用轉換換行符,又能得到與文件中一模一樣的數據。
> **備注** 轉換為二進制模式的 `IO` 對象無法再次轉換為文本模式。
### **17.5 緩沖**
即使對 `IO` 對象輸出數據,結果也并不一定馬上就會反映在控制臺或者文件中。在使用 `write`、`print` 等方法操作 `IO` 對象時,程序內部會開辟出一定的空間來保存臨時生成的數據副本(圖 17.2)。這部分空間就稱為緩沖(buffer)。緩沖里累積一定量的數據后,就會做實際的輸出處理,然后清空緩沖。

**圖 17.2 緩沖的狀態**
像這樣,使用臨時緩沖進行數據處理稱為緩沖處理(buffering)。
在向控制臺輸出的兩種方式(標準輸出與標準錯誤輸出)中,標準錯誤輸出完全不采用緩沖處理。因此,當兩種方式混合使用時,程序實際輸出的順序可能會與程序代碼中記錄的順序不一樣。
我們來看看代碼清單 17.5 的例子(不同平臺下的運行結果可能會不一樣)。
**代碼清單 17.5 test_buffering1.rb**
~~~
$stdout.print "out1 "
$stderr.print "err1 "
$stdout.print "out2 "
$stdout.print "out3 "
$stderr.print "err2\n"
$stdout.print "out4\n"
~~~
> **執行示例**
~~~
> ruby test_buffering1.rb
err1 err2
out1 out2 out3 out4
~~~
標準錯誤輸出的主要目的是輸出如警告、錯誤等信息,因此執行結果必須馬上反映出來。再次強調,建議在顯示程序中正常信息以外的信息時使用標準錯誤輸出。
雖然緩沖處理可以提高輸出效率,但有時候我們會希望執行結果可以馬上反映出來,這時我們就可以用下面的方法來同步數據的操作與輸出。
-
***io*.`flush`**
強制輸出緩沖中的數據。在代碼清單 17.5 的基礎上追加 `$stdout.flush` 的調用(代碼清單 17.6)。
**代碼清單 17.6 test_buffering2.rb**
~~~
$stdout.print "out1 "; $stdout.flush
$stderr.print "err1 "
$stdout.print "out2 "; $stdout.flush
$stdout.print "out3 "; $stdout.flush
$stderr.print "err2\n"
$stdout.print "out4\n"
~~~
> **執行示例**
~~~
> ruby test_buffering2.rb
out1 err1 out2 out3 err2
out4
~~~
-
***io*.`sync`
*io*.`sync`=(*state*)**
通過 `io.sync = true`,程序寫入緩沖時 `flush` 方法就會被自動調用。
**代碼清單 17.7 test_buffering3.rb**
~~~
$stdout.sync = true # 同步輸出處理
$stdout.print "out1 "
$stderr.print "err1 "
$stdout.print "out2 "
$stdout.print "out3 "
$stderr.print "err2\n"
$stdout.print "out4\n"
~~~
即使不逐次調用 flush 方法,也可以像下面那樣按順序輸出:
> **執行示例**
~~~
> ruby test_buffering3.rb
out1 err1 out2 out3 err2
out4
~~~
### **17.6 與命令進行交互**
雖然 Ruby 是幾乎什么都能實現的強大的語言,但是也會有與其他命令進行數據交換的時候。例如,讀取使用 GUN zip 壓縮的數據的時候,使用 gunzip 命令會很方便。在 Ruby 中,使用 `IO.popen` 方法可以與其他命令進行數據處理。
-
**IO.`popen`(*command, mode*)**
參數 *mode* 的使用方法與 `File.open` 方法是一樣的,參數缺省時默認為 `"r"` 模式。
用 `IO.popen` 方法生成的 `IO` 對象的輸入 / 輸出,會關聯啟動后的命令 *command* 的標準輸入 / 輸出。也就是說,`IO` 對象的輸出會作為命令的輸入,命令的輸出則會作為 `IO` 對象的輸入。
我們來改造一下第 3 章的 simple_grep.rb,利用 gunzip 命令解壓處理擴展名為 .gz 的文件(-c 為將解壓后的結果寫入到標準輸出時的選項)。
**代碼清單 17.8 simple_grep_gz.rb**
~~~
pattern = Regexp.new(ARGV[0])
filename = ARGV[1]
if /.gz$/ =~ filename
file = IO.popen("gunzip -c #{filename}")
else
file = File.open(filename)
end
file.each_line do |text|
if pattern =~ text
print text
end
end
~~~
> **注** 執行代碼清單 17.8 時需要 gunzip 命令。
-
**`open`("*|command*"*, mode*)**
將帶有管道符號的命令傳給 `open` 方法的效果與使用 `IO.popen` 方法是一樣的。
~~~
filename = ARGV[0]
open("|gunzip -c #{filename}") do |io|
io.each_line do |line|
print line
end
end
~~~
### **17.7 open-uri 庫**
除了控制臺、文件以外,進程間通信時使用的管道(pipe)、網絡間通信時使用的套接字(socket)也都可以作為 `IO` 對象使用。管道、套接字的使用方法超出了本書的范圍,因此不做詳細說明,這里我們簡單地介紹一下利用 HTTP、FTP 從網絡獲取數據的方法。
通過 `require` 引用 `open-uri` 庫后,我們就可以像打開普通的文件一樣打開 HTTP、FTP 的 URL(代碼清單 17.9)。使用 `open-uri` 庫的功能時,不要使用 `File.open` 方法,只使用 `open` 方法即可。
**代碼清單 17.9 read_uri.rb**
~~~
require "open-uri"
# 通過HTTP 讀取數據
open("http://www.ruby-lang.org") do |io|
puts io.read # 將Ruby 的官方網頁輸出到控制臺
end
# 通過FTP 讀取數據
url = "ftp://www.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p0.tar.gz"
open(url) do |io|
open("ruby-2.0.0-p0.tar.gz", "w") do |f| # 打開本地文件
f.write(io.read)
end
end
~~~
通過 HTTP 協議時,服務器會根據客戶端的狀態改變應答的內容,比如返回中文或英語的網頁等。為了實現這個功能,請求時就需要向服務器發送元信息(meta information)。例如,代碼清單 17.10 中的 HTTP 頭部信息 Accept-Language 就表示優先接收中文網頁。指定 HTTP 頭部信息時,會將其以散列的形式傳遞給 `open` 方法的第 2 個參數。
**代碼清單 17.10 read_uri_cn.rb**
~~~
require "open-uri"
options = {
"Accept-Language" => "zh-cn, en;q=0.5",
}
open("http://www.ruby-lang.org", options){|io|
puts io.read
}
~~~
### **17.8 stringio 庫**
在測試程序時,雖然我們會希望知道向文件或控制臺輸出了什么,但程序實際執行的結果卻往往很難知道。為此,我們可以通過向模擬 `IO` 對象的對象進行輸出來確認執行結果。
`StringIO` 就是用于模擬 `IO` 對象的對象。通過 `require` 引用 `stringio` 庫后,就可以使用 `StringIO` 對象了(代碼清單 17.11)。
**代碼清單 17.11 stringio_puts.rb**
~~~
require "stringio"
io = StringIO.new
io.puts("A")
io.puts("B")
io.puts("C")
io.rewind
p io.read #=> "A\nB\nC\n"
~~~
實際上,向 `StringIO` 對象進行的輸出并不會被輸出到任何地方,而是會被保存在對象中,之后就可以使用 `read` 方法等來讀取該輸出。
`StringIO` 對象還有另外一種用法,那就是將字符串數據當作 `IO` 數據處理。將大數據保存在文件中,并將小數據直接傳輸給別的處理時,通過使用 `StringIO` 對象,程序就可以不區分對待 `IO` 對象和字符串了。實際上,之前介紹的用 `open-uri` 庫打開 URI 時,也是有時候返回 `IO` 對象,有時候返回 `StringIO` 對象。不過一般情況下,我們不需要在意這兩者的區別。通過將數據字符串傳遞給 `StringIO.new` 方法的參數,就可以由字符串創建 `StringIO` 對象(代碼清單 17.12)。
**代碼清單 17.12 stringio_gets.rb**
~~~
require "stringio"
io = StringIO.new("A\nB\nC\n")
p io.gets #=> "A\n"
p io.gets #=> "B\n"
p io.gets #=> "C\n"
~~~
`StringIO` 對象可以模擬本章中介紹的大部分輸入 / 輸出操作。
### **練習題**
1. 創建腳本,讀取文本文件中的內容,按以下條件進行處理。這里將空白和換行以外的連續字符串稱為“單詞”。
(a)統計文本的行數
(b)統計文本的單詞數
(c)統計文本的字符數
2. 創建腳本,按照以下條件覆蓋文本文件中原有的數據。
(a)將文件中的行逆序排列
(b)保留文件中第 1 行數據,其余全部刪除
(c)保留文件中最后 1 行數據,其余全部刪除
3. 定義一個功能類似于 Unix 中的 tail 命令的 `tail` 方法。
`tail` 方法有兩個參數。
**`tail`( 行數, 文件名)**
從文件的末尾開始數,輸出參數指定的行數的內容。也就是說,假設有一個有 100 行數據的文件 some_file.txt,執行 `tail(10, "some_file.txt")` 后,程序就會跳過前 90 行數據,只在標準輸出中輸出最后 10 行數據。
> 參考答案:請到圖靈社區本書的“隨書下載”處下載([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 參考集
- 后記
- 謝辭