[Raku] 如何簡便地讀取文字檔
作者:gugod 發佈於: ,更新於: #rakulang將文字檔案內容讀取出來,都需要經過開檔、讀取、關檔這三步曲,但另外也有些較簡短的寫法,可讓人略檔開檔及關檔,讓 raku 內部自動去處理。雖然也因在使用情境上較有限制,但比較容易,也比較不會寫錯。
讀取全檔
可用 slurp
函式一次讀取全檔:
my $content = slurp("example.txt");
slurp
函式會將其參數視為檔名,並假設檔案的文字編碼為 UTF-8。
例如、讓 example.txt
內容為這兩行:
你好
Hello
將全檔讀取進來、存於 $content
中後的寫法是
my $content = slurp("example.txt");
若對 $content
進行基本檢查,就可看到 $content
內裝了 9 個字符,與預期相同。
say $content.chars;
#=> 9
say $content.comb.Array.raku;
#=> ["你", "好", "\n", "H", "e", "l", "l", "o", "\n"]
一次讀一行、一行一行讀
如果要以行單位讀檔,可用 .lines
。
從檔名做出 IO::Path
物件的寫法是透過 .IO
"example.txt".IO
這寫法有點像是型別轉換。在此物件消滅時會自動關檔。
將全檔以行單位讀取後全數裝到陣列變數 @content
中:
my @content = "example.txt".IO.lines;
say @content.raku;
#=> ["你好", "Hello"]
.lines
方法其實不會立刻就把全檔讀進來,而是會做個迭代器物件,一次讀一部份,再把讀進來的部份依換行字符切開後提供出來。如果把這迭代器物件存於陣列裡,那使用起來就與普通陣列無異。使用那陣列,就相當於是在讀檔。在取第 $i
個元素時,就會讀檔讀到第 $i
行後記著,而如果之前已經讀過第 $i
行了,就會直接取出之前的記憶,不會再次讀檔。
如果不需讀取全檔,只需要前 10 行,可透過對 .lines
取切片的方式來完成:
my @content = "example.txt".lines.[0..^10];
say @content.raku;
#=> ["你好", "Hello", Any, Any, Any, Any, Any, Any, Any, Any]
可看到,後面有很多怪怪的東西。這是因為我們的 example.txt
內容只有兩行,不到十行。因此在取切片時得到了很多「未定義值」。可利用 .grep(&defined)
將那些值去掉:
my @content = "example.txt".lines.[0..^10].grep(&defined);
say @content.raku;
#=> ["你好", "Hello"]
如果所需要的是第 10 行到第 19 行,一樣可透過對 .lines
取切片的方式來完成:
my @content = "example.txt".lines.[10..19];
如果所需要的是「最後 5 行」,可在取切片時使用 *
算符。此時若 example.txt
內容不到 5 行,則 @content
會是空的:
my @content = "example.txt".lines.[*-5..*];
在上述幾種寫法裡,@content
會對應到一個迭代器,就算是取得 .lines
的切片後,也是得到一個只會迭代十個字串的迭代器,在第一次取值時,才會真的進行讀檔。要讓其立刻讀檔,使 @content
內容物裝的是字串,加上 .Array
:
@content = "example.txt".lines.Array;
若 "example.txt" 本身有 50GB 那麼大,.lines.Array
會讓 raku 立刻把 50GB 讀進來,而 .lines
則是會將讀取的過程稍微往後延。若後方的程式裡只需要看檔案前十行就結束,那這兩種寫法所花費的記憶體量就會差很多。