Bash One-Liners Explained(一):檔案處理

發表於2013-10-21

Bash One-Liners Explained 是一系列介紹 Bash 命令技巧的文章,由國外牛人 Peteris Krumins 撰寫。憑藉紮實的功底和豐富的經驗,作者總結了許多快速解決問題的技巧,並且每一條都只要用簡潔的一行 Bash 命令就可以完成,同時每一行命令文中都給出了非常詳盡的解釋。

Peteris Krumins 是一位高產的博主,在他的部落格上有很多非常精彩的文章,推薦大家有機會都可以去好好讀一讀。例如,大家耳熟能詳的 Awk One-Liners ExplainedSed One-Liners Explained 等等。後者我也曾經在部落格上分享過一篇筆記

回到正題,雖然這一系列文章不難,但是還是可以從中學到很多細節的知識,相信這些肯定會對許多初學者有所幫助,所以我打算將這一系列翻譯成中文,分享給大家。為了同原文保持一致,這一系列文章最終會分成以下五篇:

  1. Bash One-Liners Explained 譯文(一): 檔案處理
  2. Bash One-Liners Explained 譯文(二): 操作字串
  3. Bash One-Liners Explained 譯文(三): 漫談重定向;
  4. Bash One-Liners Explained 譯文(四): 歷史命令;
  5. Bash One-Liners Explained 譯文(五): 命令列跳轉;

本系列的文章同其它系列一樣,最終都可以在連載頁面找到,有興趣的同學可以隨意翻翻,看看有沒有一些對你有價值的文章,大家一起交流學習。

1. 清空檔案內容

這一行命令用到了輸出重定向操作符>。輸出重定向發生時,檔案會被開啟準備寫入。如果此時檔案不存在則先建立,存在則將其大小擷取為0。這裡我們並沒有重定向寫任何內容到檔案中,所以檔案依然保持為空。

如果你想替換檔案的內容,或者建立一個包含指定內容的檔案,可以執行下面的命令:

2. 追加內容到檔案

這一行命令用到了另外一個輸出重定向操作符>>,該操作符將內容追加到檔案。同樣地,如果檔案不存在則先建立它。追加的內容之後,緊跟著換行符。如果你不想要追加換行符,在執行echo命令時可以指定-n選項:

 3. 讀取檔案的首行並賦值給變數

這一行命令用到了 Bash 的內建命令read,和輸入重定向操作符<read命令從標準輸入中讀取一行,並將內容儲存到變數line中。在這裡,-r選項保證讀入的內容是原始的內容,意味著反斜槓轉義的行為不會發生。輸入重定向操作符< file開啟並讀取檔案file,然後將它作為read命令的標準輸入。

記住,read命令會刪除包含在IFS變數中出現的所有字元,IFS 的全稱是 Internal Field Separator,Bash 根據 IFS 中定義的字元來分隔單詞。在這裡,read命令讀入的行被分隔成多個單詞。預設情況下,IFS包含空格,製表符和回車,這意味著開頭和結尾的空格和製表符都會被刪除。如果你想保留這些符號,可以通過設定IFS為空來完成:

IFS 的變化僅會影響當前的命令,這行命令可以保證讀入原始的首行內容到變數line中,同時行首與行尾的空白字元被保留。

另外一種讀取檔案首行內容,並賦值給變數的方法是:

這裡用到了命令替換操作符$(...),它執行括號裡的命令並且將輸出返回。 這個例子中,命令是head -1 file,輸出的內容是檔案的首行。輸入然後通過等號賦值給變數line$(...)的等價寫法是...,所以也可以換成下面這樣:

不過,在 Bash 中$(...)用法更加推薦,因為它看起來更加整潔,並且容易巢狀使用。

4. 依次讀入檔案每一行

這是一種正確的讀取檔案內容的做法,read命令放在while迴圈中。當read命令遇到檔案結尾時(EOF),它會返回一個正值,導致迴圈判斷失敗終止。

記住,read命令會刪除首尾多餘的空白字元,所以如果你想保留,請設定 IFS 為空值:

如果你不想將< file放在最後,可以通過管道將檔案的內容輸入到 while 迴圈中:

5. 隨機讀取一行並賦值給變數

Bash 中並沒有提供一種直接的方法來隨機讀取檔案的某一行內容,所以這裡需要利用外部程式。在最新的一些 Linux 系統上,GNU Coreutils 包中提供的shuf命令可以滿足我們的需求。

這一行命令中用到了程式替換(process substitution)操作符<(...)。程式替換操作會建立一個匿名的管道檔案,並將程式命令的標準輸出連線到管道的寫一端。然後 Bash 開始執行程式替換中的命令,然後將整個程式替換的表示式替換成匿名管道的檔名。

當 Bash 看到<(shuf file)時,它首先開啟一個特殊的檔案/dev/fd/n,這裡的n是一個空閒的檔案描述符,然後執行shuf file命令,將標準輸出連線到/dev/fd/n,並且替換<(shuf file)/dev/fd/n,因此實際的命令會變成:

結果會讀取洗牌後的檔案的第一行內容。

另外一種做法是,使用 GNU sort 命令,它提供的-R選項可以隨機排序檔案:

或者,同前面一樣,將結果賦值給變數:

這裡,我們首先通過sort -R隨機排序檔案,然後通過head -1 讀取檔案的第一行。

6. 讀取檔案首行前三個欄位並賦值給變數

如果在read命令中指定多個變數名,它會將讀入的內容分隔成多個欄位,然後依次賦值給對應的變數,第一個欄位賦值給第一個變數,第二個欄位賦值給第二個變數,等等,最後將剩餘的所有欄位賦值給最後一個變數。這也是為什麼,在上面的例子中,我們加了一個throwaway變數,否則的話,當檔案的一行大於三個欄位時,第三個變數的內容會包含所有剩餘的欄位。

有時候,為了書寫方便,可以簡單地用_來替換throwaway變數:

又或者,如果你的檔案確實只有三個欄位,那可以忽略它:

下面是一個例子,假如你想知道一個檔案到底包含多少行,多少個單詞以及多少個位元組。當你執行wc命令時,你會得到3個數字加上檔名,檔名在最後:

所以,這個檔案包含5行,10個單詞,以及20個字元。我們接下來,可以通過read命令將這些資訊儲存到變數中:

類似地,你也可以使用 here-strings 將字串分隔並儲存到變數中。假設你有一個字串變數$info,內容為"20 packets in 10 seconds",然後你想要將從中獲取2010。在不久之前,我是這樣來完成的:

 

然而,得益於read命令的強大和對 Bash 的瞭解,我們可以這樣做:

這裡,<<< 就是 here-string 的語法,它允許你直接傳遞字串給標準輸入。

7. 儲存檔案的大小到變數

這一行命令中用到了第3點中介紹的命令替換操作$(...),它執行裡面的命令並將結果獲取回來。在這個例子中,命令是wc -c < file,它輸出檔案的位元組數。這個結果最終會賦值給變數size

8. 從檔案路徑中獲取檔名

假設,你有一個檔案,它的路徑為/path/to/file.ext,然後你要從中獲取檔名,在這裡是file.ext。你要怎麼做? 一個好的方法是通過引數展開(parameter expansion)功能:

這一行命令使用了引數展開的語法:${var##pattern},它從$var字串開始處開始匹配pattern。如果能夠匹配成功,將最長匹配的內容刪除後再返回。

在這個例子中,匹配的模式是*/,它嘗試匹配/path/to/file.ext的開始部分,正如前面所說,這裡是貪婪匹配,所以它能夠匹配到最後一個斜槓為止,即匹配的內容是/path/to/。所以當把匹配的內容刪除後,返回的內容就是檔名file.ext

9. 從檔案路徑中獲取目錄名

和上面一樣類似,這次你要從路徑/path/to/file.txt中獲取目錄名/path/to。你可以繼續通過引數展開功能來完成這個任務:

這次的用法是${var%pattern},它從$var的結尾處匹配/*。如果能夠成功匹配,將最短匹配的內容刪除再返回。

在這個例子中,匹配的模式是/*,它能夠匹配/file.ext部分,刪除這部分內容後返回的就是目錄名稱。

10. 快速拷貝檔案

假設你要將檔案/path/to/fil拷貝到/path/to/file_copy,一般情況下,大多數人會這麼來寫:

不過,你可以利用括號展開(brace expansion{...}功能:

括號展開可以生成任意字串的組合,在這個例子中,/path/to/file{,_copy}最終生成/path/to/file /path/to/file_copy。所以上面這行命令最終髮型成:

類似地,你可以執行下面的命令快速的移動檔案:

這行命令展開後就變成了:

本文完

相關文章