導讀
之前我們也偶爾接觸過讀寫檔案的方法,本篇會系統講讀寫檔案的各種方法。
寫檔案
寫檔案要比讀檔案簡單一些,最常用的用法是使用 > 直接將命令的輸出重定向到檔案。如果檔案存在,內容會被覆蓋;如果檔案不存在,會被建立。
% 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。