12 文字處理三劍客
awk、sed、grep 是 Unix 文字處理的經典工具。雖然有現代替代品,但理解它們仍然重要。
12.1 grep:搜尋文字
12.1.1 基本用法
背景(問題發現)
在大量文字檔案中尋找特定內容是日常開發中最常見的任務之一。無論是查找程式碼中的函數定義、搜尋設定檔中的特定參數,或是在 log 檔中追蹤錯誤訊息,我們都需要一個快速且可靠的搜尋工具。手動瀏覽檔案既耗時又容易遺漏,特別是在處理數百行甚至數千行的檔案時。
方法
grep (Global Regular Expression Print) 透過逐行掃描檔案,將符合指定 pattern(模式)的行印出。核心概念是: - 預設進行逐行匹配,找到包含 pattern 的整行就輸出 - 支援多種選項調整搜尋行為(忽略大小寫、顯示行號等) - 可對單一檔案或整個目錄樹進行搜尋
結果(程式碼)
討論/延伸
- 注意事項:
grep預設區分大小寫,記得在需要時使用-i選項 - 效能考量:遞迴搜尋大型目錄時可能很慢,考慮使用現代替代品
ripgrep - 常見組合:
grep -rn結合遞迴與行號,是最常用的組合 - 進階學習:可搭配正規表示式進行更複雜的模式匹配(見下一節)
12.1.2 正規表示式
背景(問題發現)
簡單的字串匹配往往不夠靈活。例如,我們可能需要同時搜尋多個相關詞彙(如 “error” 或 “warning”),或是只想匹配完整單詞而非單詞的一部分(搜尋 “test” 時不希望匹配到 “testing”)。此外,在閱讀搜尋結果時,常常需要看到匹配行的上下文才能理解完整意義。
方法
grep 支援正規表示式(Regular Expression)來實現更精確的匹配: - 延伸正規表示式 (-E):允許使用 |(或)、+(一次以上)等進階語法 - 單詞邊界匹配 (-w):確保只匹配完整單詞,避免部分匹配 - 上下文顯示:透過 -A(After)、-B(Before)、-C(Context)選項顯示匹配行周圍的內容
結果(程式碼)
討論/延伸
- 正規表示式變體:基本正規表示式(BRE)與延伸正規表示式(ERE)語法略有差異,建議使用
-E獲得更直觀的語法 - 實用範例:
grep -E "error|warning|critical"可同時搜尋多種錯誤層級 - 除錯技巧:使用
--color=auto高亮顯示匹配的部分,更容易看出匹配邏輯 - 效能提示:複雜的正規表示式會影響搜尋速度,簡單情境下使用固定字串即可
- 進階學習:掌握
^(行首)、$(行尾)、.*(任意字元)等正規表示式基礎
12.2 sed:串流編輯器
12.2.1 取代文字
背景(問題發現)
批次取代文字是常見需求:重新命名變數、更新 API 端點、修正拼寫錯誤等。手動逐一修改既費時又容易出錯,特別是需要在多個檔案中進行相同替換時。我們需要一個能夠自動化這個過程的工具,並且要能精確控制替換範圍(是只替換第一次出現,還是所有出現)。
方法
sed (Stream Editor) 是一個串流編輯器,核心概念是「讀取 → 處理 → 輸出」的管線模式: - 基本語法:s/pattern/replacement/ 中的 s 代表 substitute(替換) - 全域替換:加上 g (global) 旗標會替換每行中的所有匹配,而非只有第一個 - 原地修改:-i 選項直接修改原檔案,否則只輸出到標準輸出 - 跨平台差異:macOS 需要 -i '',Linux 則是 -i 即可
結果(程式碼)
討論/延伸
- 安全建議:使用
-i前先測試指令輸出,確認替換結果正確後再實際修改檔案 - 備份機制:
sed -i.bak會在修改前建立.bak備份檔 - 正規表示式:pattern 部分支援正規表示式,如
sed 's/[0-9]*/X/g'替換所有數字 - 分隔符彈性:可使用其他字元作為分隔符,如
sed 's|/old/path|/new/path|'處理路徑更清晰 - 現代替代:考慮使用
sd工具,語法更直觀且預設就是全域替換 - 批次處理:搭配
find對多個檔案批次替換:find . -name "*.txt" -exec sed -i 's/old/new/g' {} \;
12.2.2 刪除行
背景(問題發現)
處理文字檔時經常需要移除不需要的內容:刪除空白行以壓縮檔案、移除特定的標記行、或是去除測試資料。手動刪除容易遺漏,特別是當空白行或目標行分散在大檔案中時。我們需要一個能夠基於行號或內容模式來批次刪除行的方法。
方法
sed 的刪除操作使用 d 指令,可以搭配不同的定位方式: - 行號定位:直接指定行號(如 3d 刪除第三行) - 模式匹配:使用正規表示式匹配內容(如 /pattern/d 刪除包含 pattern 的行) - 特殊模式:/^$/ 代表「行首接著行尾」,即空白行 - 處理邏輯:符合條件的行會被從輸出中排除,不影響原檔案(除非使用 -i)
結果(程式碼)
討論/延伸
- 範圍刪除:
sed '3,5d'刪除第 3 到第 5 行,sed '3,$d'刪除第 3 行到檔尾 - 反向刪除:
sed '/pattern/!d'只保留包含 pattern 的行(等同於grep pattern) - 組合條件:可以串接多個刪除操作,如
sed '/^#/d; /^$/d'同時刪除註解和空白行 - 實用情境:清理 log 檔、移除程式碼註解、過濾資料等
- 替代方案:簡單的過濾任務用
grep -v可能更直觀 - 注意事項:刪除操作不可逆,使用
-i前務必測試或備份
12.2.3 插入和追加
背景(問題發現)
在編輯設定檔或程式碼時,我們常需要在特定位置插入新內容:在檔案開頭加上版權聲明、在特定函數後添加新邏輯、或在配置區塊中插入新參數。手動編輯多個檔案既繁瑣又容易出錯,特別是需要在相同位置進行相同插入時。
方法
sed 提供兩種插入方式,差別在於插入位置: - append (a\):在指定行之後插入新內容 - insert (i\):在指定行之前插入新內容 - 定位方式:可用行號(如 2)或模式(如 /pattern/)指定位置 - 跳脫符號:\ 用於標示接下來是要插入的文字內容
結果(程式碼)
討論/延伸
- 多行插入:某些版本支援
sed '2a\第一行\n第二行'插入多行內容 - 模式定位:
sed '/config_start/a\new_config=value'在包含 “config_start” 的行後插入 - 檔首檔尾:
sed '1i\header'在檔首插入,sed '$a\footer'在檔尾追加 - 實用範例:在 Python 檔案開頭加上 shebang:
sed '1i\#!/usr/bin/env python3' - 平台差異:BSD sed (macOS) 和 GNU sed (Linux) 在多行處理上略有不同
- 注意事項:插入內容中的特殊字元(如
$、&)可能需要跳脫 - 替代工具:簡單的插入任務可考慮用文字編輯器或
echo配合重導向
12.3 awk:資料處理
12.3.1 基本結構
背景(問題發現)
處理結構化文字資料(如 log 檔、CSV、設定檔)時,我們經常需要提取特定欄位、進行條件過濾、或執行統計計算。雖然 grep 和 sed 很強大,但它們主要處理整行或字串,對於「欄位導向」的資料處理並不直觀。我們需要一個能夠理解資料結構,並能以欄位為單位進行操作的工具。
方法
awk 是一個專為文字處理設計的程式語言,核心概念是「逐行讀取,按欄位處理」: - 基本語法:pattern { action } 表示「當符合 pattern 時,執行 action」 - 自動分欄:awk 會自動將每行分割成欄位,$1 代表第一欄、$2 第二欄,以此類推 - 預設行為:若省略 pattern,則處理所有行;若省略 action,則印出符合的行 - 執行流程:讀取一行 → 檢查 pattern → 執行 action → 重複直到檔案結束
結果(程式碼)
討論/延伸
- 最簡範例:
awk '{print $1}'印出每行的第一個欄位(pattern 和 action 都可省略部分) - pattern 類型:可以是正規表示式(如
/error/)、條件(如$2 > 100)、或特殊關鍵字(如BEGIN、END) - action 可能性:不只 print,還能做運算、變數操作、條件判斷等完整程式邏輯
- 與其他工具比較:awk 適合欄位處理,grep 適合行匹配,sed 適合文字替換
- 學習曲線:awk 實際上是完整的程式語言,本章只介紹最實用的部分
- 進階學習:掌握
BEGIN(前處理)、END(後處理)、內建變數(NR、NF)等概念
12.3.2 欄位處理
背景(問題發現)
許多資料檔案是以空白或特定字元分隔的欄位格式:系統的 /etc/passwd 使用冒號分隔、CSV 檔案使用逗號、log 檔可能使用空白。我們常需要提取其中特定欄位來分析,例如從 log 中取出時間戳記和錯誤訊息,或從使用者列表中提取帳號名稱。手動處理既費時又容易出錯。
方法
awk 的欄位處理機制非常直覺: - 欄位變數:$1、$2、$3 等代表第 1、2、3 欄,$0 代表整行 - 預設分隔符:空白(space 或 tab)會自動作為欄位分隔符 - 自訂分隔符:使用 -F 選項指定(如 -F: 表示用冒號分隔) - 輸出格式:多個欄位用逗號分隔會自動加上空格,直接連接則無空格
結果(程式碼)
討論/延伸
- 實用範例:
awk -F: '{print $1}' /etc/passwd列出系統所有使用者名稱 - 欄位數量:內建變數
NF儲存當前行的欄位總數,$NF即最後一欄 - 格式化輸出:
awk '{print "Name: " $1 ", Age: " $2}'可加上標籤文字 - 多字元分隔符:
-F' '(兩個空格)或-F'[, ]'(逗號或空格,使用正規表示式) - 輸出分隔符:使用
OFS變數改變輸出分隔符,如awk 'BEGIN{OFS=","} {print $1,$2}'輸出 CSV - 常見錯誤:注意欄位編號從 1 開始(不是 0),
$0才是整行 - 除錯技巧:先用
awk '{print NF, $0}'確認分欄結果是否符合預期
12.3.3 計算
背景(問題發現)
分析資料時經常需要進行統計計算:計算 log 檔中的總請求數、平均回應時間、銷售資料的總額等。雖然可以將資料匯入試算表或寫程式處理,但對於簡單的統計任務來說過於繁瑣。我們需要一個能在命令列上快速進行累加、計數、平均等基本統計的方法。
方法
awk 允許在處理每一行時進行運算,並在最後輸出結果: - 累加模式:sum += $1 表示將第一欄的值累加到 sum 變數中 - 計數器:count++ 每處理一行就加 1,用於計算總行數 - END 區塊:END { } 中的程式碼在所有行處理完後才執行,適合輸出最終結果 - 變數初值:awk 的變數預設為 0(數值)或空字串(文字),不需明確初始化
結果(程式碼)
討論/延伸
- 計算特定欄:
awk '{sum += $3}' END {print sum}'計算第三欄總和 - 內建變數 NR:可用
NR取代count,因為它自動記錄處理的行數:awk '{sum += $1} END {print sum/NR}' - 多重統計:
awk '{sum+=$1; min=($1<min||!min)?$1:min; max=($1>max)?$1:max} END {print sum/NR, min, max}'同時計算平均、最小、最大值 - 條件統計:
awk '$2=="OK" {count++} END {print count}'只統計第二欄為 “OK” 的行數 - 實用範例:分析網站 log 的流量統計、計算成績平均、統計錯誤次數等
- 精度控制:使用
printf格式化輸出:END {printf "%.2f\n", sum/NR}顯示到小數點後兩位 - 除以零錯誤:處理可能空檔案時要檢查:
END {if (NR>0) print sum/NR; else print 0}
12.3.4 條件過濾
背景(問題發現)
在分析資料時,我們常需要根據數值條件篩選記錄:找出超過閾值的異常數據、過濾特定狀態的請求、或根據時間範圍擷取 log。雖然 grep 能做文字匹配,但對於數值比較(大於、小於、等於)或複雜邏輯條件就力不從心了。我們需要能同時進行模式匹配和數值比較的過濾能力。
方法
awk 的條件過濾結合了模式匹配和程式邏輯: - 數值比較:可直接在 pattern 部分使用比較運算子(>、<、>=、<=、==、!=) - 正規表示式:/pattern/ 匹配整行,$n ~ /pattern/ 匹配特定欄位 - 邏輯組合:可用 &&(且)、||(或)、!(非)組合多個條件 - 預設 action:當 pattern 符合但沒有 action 時,預設會 print 整行
結果(程式碼)
討論/延伸
- 簡化寫法:
awk '$2 > 100'即可,不用寫{print},因為預設動作就是印出 - 複雜條件:
awk '$2 > 100 && $3 == "ERROR"'同時檢查多個欄位 - 欄位匹配:
awk '$1 ~ /^[0-9]/ {print}'只印出第一欄以數字開頭的行 - 範圍過濾:
awk '$2 >= 50 && $2 <= 100'選出第二欄在 50-100 之間的行 - 字串比較:
awk '$3 == "SUCCESS"'過濾第三欄為特定文字的行(注意要用雙引號) - 實用案例:
awk '$9 >= 400' access.log找出 HTTP 狀態碼 4xx/5xx 的錯誤請求 - 效能優化:條件寫在前面可提早過濾,減少不必要的處理
- 與 grep 比較:awk 條件過濾可替代很多 grep 場景,且能同時處理欄位
12.4 實際範例
12.4.1 分析 log 檔
背景(問題發現)
網站伺服器的 access log 包含大量請求記錄,我們需要從中提取有用資訊:了解哪些 HTTP 狀態碼最常出現(是否有大量錯誤)、識別最活躍的 IP 位址(可能是爬蟲或攻擊來源)等。手動閱讀數萬筆 log 不切實際,我們需要自動化的統計分析方法。
方法
結合 awk 與其他 Unix 工具進行 log 分析管線: - 欄位擷取:awk '{print $9}' 提取特定欄位(如 HTTP 狀態碼) - 排序分組:sort | uniq -c 將相同值分組並計數 - 排序結果:sort -rn 按數值反向排序(-r 反向,-n 數值) - 限制輸出:head -10 只顯示前 10 筆結果
結果(程式碼)
討論/延伸
- 管線概念:Unix 管線(pipe
|)將多個簡單工具串接成強大功能 - 避免 useless cat:可簡化為
awk '{print $9}' access.log | sort | uniq -c | sort -rn - log 格式:Apache/Nginx 的 Combined Log Format 中,
$1是 IP,$9是狀態碼 - 進階統計:加上
awk '{sum+=$1} END {print sum}'計算總請求數 - 時間範圍:可先用 awk 條件過濾特定時間:
awk '$4 ~ /\[01\/Jan\/2024/' - 視覺化:輸出可導向檔案後用 gnuplot 或試算表繪圖
- 效能提升:處理大檔案時,
awk統計比管線更快:awk '{status[$9]++} END {for (s in status) print status[s], s}' - 實用延伸:分析回應時間、計算頻寬使用、偵測異常流量模式
12.4.2 處理 CSV
12.4.3 歷史命令分析
12.5 現代替代品
在日常使用中,可以考慮:
| 傳統工具 | 現代替代 |
|---|---|
grep |
ripgrep |
sed |
sd |
awk |
miller (mlr) |
但理解傳統工具仍然重要,因為它們在任何 Unix 系統上都可用。
12.6 實作練習
- 用 grep 搜尋你的設定檔中所有 export 行
- 用 sed 批次取代檔案中的字串
- 用 awk 分析你的 shell 歷史記錄
日常使用 ripgrep,但保持對 grep/sed/awk 的基本理解,在沒有現代工具的伺服器上會很有用。