Zsh 開發指南(第十四篇 檔案讀寫)

陌辭寒發表於2019-01-07

導讀

之前我們也偶爾接觸過讀寫檔案的方法,本篇會系統講讀寫檔案的各種方法。

寫檔案

寫檔案要比讀檔案簡單一些,最常用的用法是使用 > 直接將命令的輸出重定向到檔案。如果檔案存在,內容會被覆蓋;如果檔案不存在,會被建立。

% echo abc > test.txt複製程式碼

如果不想覆蓋之前的檔案內容,可以追加寫入:

% echo abc >> test.txt複製程式碼

這樣如果檔案存在,內容會被追加寫入進去;如果檔案不存在,也會被建立。

建立檔案

有時我們只想先建立個檔案,等以後需要的時候再寫入。

touch 命令用於建立檔案(普通檔案):

% touch test1.txt test2.txt

# 或者用 echo 輸出重定向,效果和 touch 一樣
# 加 -n 是因為不加的話 echo 會輸出一個換行符
% echo -n >>test1.txt >>test2.txt

# 或者使用輸入重定向
% >>test1.txt >>test2.txt </dev/null

# mkdir 用來建立目錄,如果需要在新目錄建立檔案
% mkdir dir1 dir2複製程式碼

如果檔案已經存在,touch 命令會更新它的時間(mtime、ctime、atime 一起更新,其餘兩種方法不會)到當前時間。另外下邊的清空檔案方法,也都可以用來建立檔案。touch 命令的使用比較方便,但如果想盡量少依賴外部命令,可以使用後兩種方法。

因為檔案建立過程通常不存在效能瓶頸,不用過多考慮效能因素。如果需要建立大量檔案,可以在自己的環境分別用這幾種方法試驗幾次,看需要多少時間。

我在樹莓派 3B 簡單測試一下:

# 三個指令碼,分別建立 1000 個檔案
% cat test1 test2 test3
#!/bin/zsh

touch test1{1..1000}.txt
#!/bin/zsh

echo -n >>test2{1..1000}.txt
#!/bin/zsh

>>test3{1..1000}.txt </dev/null複製程式碼
# 執行了幾次,結果差不多
% time ./test1; time ./test2; time ./test3
./test1  0.02s user 0.03s system 86% cpu 0.058 total
./test2  0.02s user 0.02s system 70% cpu 0.056 total
./test3  0.03s user 0.01s system 72% cpu 0.055 total複製程式碼

另外如果檔案數量太多的話,方法二、三要按批次建立,因為一個程式能開啟的 fd 總數是有上限的。

清空檔案

有時我們需要清空一個現有的檔案:

# 使用 echo 輸出重定向
% echo -n >test.txt

# 使用輸入重定向
% >test.txt </dev/null

# 也可以使用 truncate 命令清空檔案
% truncate -s 0 test.txt複製程式碼

通常使用第一種方法即可,比較簡單易懂。非特殊場景儘量不要用像 truncate 這樣不常見的命令。

刪除檔案

刪除檔案的方法比較單一,用 rm 命令即可。

% rm test1.txt test2.txt

# -f 引數代表即使檔案不存在也不報錯
% rm -f test1.txt test2.txt

# -r 引數可以遞迴刪除目錄和檔案
% rm -r dir1 dir2 test*.txt

# -v 引數代表 rm 會輸出刪除檔案的過程
% rm -v test*.txt
removed `test1.txt`
removed `test2.txt`複製程式碼

刪除檔案必須藉助 rm 命令。如果一定要不依賴外部命令的話,zsh/files 模組裡也有一個 rm 命令,可以用 zmodload zsh/files 載入,然後 rm 就變成了內部命令,用法基本相同。

% zmodload zsh/files
% which -a rm
rm: shell built-in command
/usr/bin/rm複製程式碼

此外 zsh/files 中還有內建的 chgrp、chown、ln、mkdir、mv、rmdir、sync 命令。如果不想依賴外部命令,或者系統環境出問題了用不了外部命令,可以使用這些。這可以作為命令不存在或者因為命令本身問題執行異常的一個 fallback 方案,來提高指令碼的健壯性。

多行文字寫入

通常我們寫檔案時不會每一行都單獨寫入,這樣效率太低。

可以先把字串拼接起來,然後一次性寫入,這樣比多次寫入效率更高:

% str=ab
% str+="
cd"
% str +="
$str"

echo $str > test.txt複製程式碼

可以直接把陣列寫入到檔案,每行一個元素:

% array=(aa bb cc)

% print -l $array > test.txt複製程式碼

如果是將一段內容比較固定的字串寫入到檔案,可以這樣:

# 在指令碼中也是如此,第二行以後的行首 > 代表換行,非輸入內容
# <<EOF 代表遇到 EOF 時會終止輸入內容
# 裡邊也可以使用變數
% > test.txt <<EOF
> aa
> bb
> cc dd
> ee
> EOF

% cat test.txt
aa
bb
cc dd
ee複製程式碼

用 mapfile 讀寫檔案

如果不喜歡使用重定向符號,還可以用雜湊表來操作檔案。Zsh 有一個 zsh/mapfile 模組,用起來很方便:

% zmodload zsh/mapfile

# 這樣就可以建立檔案並寫入內容,如果檔案存在則會被覆蓋
% mapfile[test.txt]="ab cd"
% cat test.txt
ab cd

# 判斷檔案是否存在
% (($+mapfile[test.txt])) && echo good
good

# 讀取檔案
% echo $mapfile[test.txt]
ab cd

# 刪除檔案
% unset "mapfile[test.txt]"

# 遍歷檔案
% for i (${(k)mapfile}) {
> echo $i
> }
test1.txt
test2.txt複製程式碼

從檔案中間位置寫入

有時我們需要從一個檔案的中間位置(比如從第 100 的字元或者第三行開始)繼續寫入,覆蓋之後的內容。Zsh 並不直接提供這樣的方法,但我們可以迂迴實現,先用 truncate 命令把檔案截斷,然後追加寫。如果檔案後邊的內容還需要保留,可以在截斷之前先讀取進來(見下文讀檔案部分的例子),最後再寫回去。

% echo 1234567890 > test.txt
# 只保留前 5 個字元
% truncate -s 5 test.txt
% cat test.txt
12345 
% echo abcde >> test.txt
% cat test.txt
12345abcde複製程式碼

讀檔案

讀取整個檔案

讀取整個檔案比較容易:

% str=$(<test.txt)
% echo $str
aa
bb
cc dd
ee複製程式碼

按行遍歷檔案

如果檔案比較大,那讀取整個檔案會消耗很多資源,可以按行遍歷檔案內容:

% while {read i} {
> echo $i
> } <test.txt
aa
bb
cc dd
ee複製程式碼

read 命令是從標準輸入讀取一行內容,把標準輸入重定向後,就變成了從檔案讀取。

讀取指定行

如果只需要讀取指定的某行或者某些行,不需要用上邊的方法加自己計數。

# (f)2 是讀取第二行
% echo ${"$(<test.txt)"[(f)2]}
bb複製程式碼

讀取檔案到陣列

讀取檔案內容到陣列中,每行是陣列的一個元素:

% array=(${(f)"$(<test.txt)"})複製程式碼

讀取指定數量的字元

有時我們需要按位元組數來讀取檔案內容,而不是按行讀取。

% cat test.txt
1234567890
# -k5 是隻最多讀取 5 個位元組,-u 0 是從 fd 0 讀取,不然會卡住
% read -k 5 -u 0 str <test.txt
% echo $str
12345複製程式碼

向檔案中間插入內容

有時我們會遇到比較麻煩的場景,在某個檔案中間插入一些內容,而前後的內容保持不變。

Zsh 並沒有直接提供這樣的功能,但我們可以迂迴實現。

% echo -n 1234567890 > test.txt
# 先全部讀進來
% str=$(<test.txt)
# 截斷檔案
% truncate -s 5 test.txt
# 插入內容
% echo -n abcde >> test.txt
# 將後半部分檔案追加回去
% echo -n $str[6,-1] >> test.txt
% cat test.txt
12345abcde67890複製程式碼

但如果比較比較大的話,就不能將整個檔案全部讀進來,可以先在迴圈裡用 read -k num 一次讀固定數量的字元,然後寫入一箇中間檔案,然後再 truncate 原檔案,插入內容。最後再 cat 中間檔案 >> 原檔案 追加原來的後半部分內容即可。

另外這種從檔案中間寫入或者讀取內容的場景,都可以使用 dd 命令實現,可以自行搜尋 dd 命令的用法。

總結

本文比較詳細地介紹了各種讀寫檔案的方法,基本可以覆蓋常用的場景。

全系列文章地址:github.com/goreliu/zsh…

付費解決 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等領域相關問題,靈活定價,歡迎諮詢,微信 ly50247。

相關文章