12  文字處理三劍客

awksedgrep 是 Unix 文字處理的經典工具。雖然有現代替代品,但理解它們仍然重要。

12.1 grep:搜尋文字

12.1.1 基本用法

背景(問題發現)

在大量文字檔案中尋找特定內容是日常開發中最常見的任務之一。無論是查找程式碼中的函數定義、搜尋設定檔中的特定參數,或是在 log 檔中追蹤錯誤訊息,我們都需要一個快速且可靠的搜尋工具。手動瀏覽檔案既耗時又容易遺漏,特別是在處理數百行甚至數千行的檔案時。

方法

grep (Global Regular Expression Print) 透過逐行掃描檔案,將符合指定 pattern(模式)的行印出。核心概念是: - 預設進行逐行匹配,找到包含 pattern 的整行就輸出 - 支援多種選項調整搜尋行為(忽略大小寫、顯示行號等) - 可對單一檔案整個目錄樹進行搜尋

結果(程式碼)

# 搜尋包含 pattern 的行
grep "pattern" file.txt

# 忽略大小寫
grep -i "pattern" file.txt

# 顯示行號
grep -n "pattern" file.txt

# 反向搜尋(不包含 pattern 的行)
grep -v "pattern" file.txt

# 遞迴搜尋
grep -r "pattern" ./

討論/延伸

  • 注意事項grep 預設區分大小寫,記得在需要時使用 -i 選項
  • 效能考量:遞迴搜尋大型目錄時可能很慢,考慮使用現代替代品 ripgrep
  • 常見組合grep -rn 結合遞迴與行號,是最常用的組合
  • 進階學習:可搭配正規表示式進行更複雜的模式匹配(見下一節)

12.1.2 正規表示式

背景(問題發現)

簡單的字串匹配往往不夠靈活。例如,我們可能需要同時搜尋多個相關詞彙(如 “error” 或 “warning”),或是只想匹配完整單詞而非單詞的一部分(搜尋 “test” 時不希望匹配到 “testing”)。此外,在閱讀搜尋結果時,常常需要看到匹配行的上下文才能理解完整意義。

方法

grep 支援正規表示式(Regular Expression)來實現更精確的匹配: - 延伸正規表示式 (-E):允許使用 |(或)、+(一次以上)等進階語法 - 單詞邊界匹配 (-w):確保只匹配完整單詞,避免部分匹配 - 上下文顯示:透過 -A(After)、-B(Before)、-C(Context)選項顯示匹配行周圍的內容

結果(程式碼)

# 使用延伸正規表示式
grep -E "pattern1|pattern2" file.txt

# 只匹配完整單詞
grep -w "word" file.txt

# 顯示前後文
grep -C 2 "pattern" file.txt  # 前後各 2 行
grep -A 2 "pattern" file.txt  # 後 2 行
grep -B 2 "pattern" file.txt  # 前 2 行

討論/延伸

  • 正規表示式變體:基本正規表示式(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 即可

結果(程式碼)

# 取代第一個匹配
sed 's/old/new/' file.txt

# 取代所有匹配
sed 's/old/new/g' file.txt

# 直接修改檔案
sed -i '' 's/old/new/g' file.txt  # macOS
sed -i 's/old/new/g' file.txt     # Linux

討論/延伸

  • 安全建議:使用 -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

結果(程式碼)

# 刪除第 3 行
sed '3d' file.txt

# 刪除空白行
sed '/^$/d' file.txt

# 刪除包含 pattern 的行
sed '/pattern/d' file.txt

討論/延伸

  • 範圍刪除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/)指定位置 - 跳脫符號\ 用於標示接下來是要插入的文字內容

結果(程式碼)

# 在第 2 行後插入
sed '2a\新的一行' file.txt

# 在第 2 行前插入
sed '2i\新的一行' file.txt

討論/延伸

  • 多行插入:某些版本支援 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 'pattern { action }' file.txt

討論/延伸

  • 最簡範例awk '{print $1}' 印出每行的第一個欄位(pattern 和 action 都可省略部分)
  • pattern 類型:可以是正規表示式(如 /error/)、條件(如 $2 > 100)、或特殊關鍵字(如 BEGINEND
  • action 可能性:不只 print,還能做運算、變數操作、條件判斷等完整程式邏輯
  • 與其他工具比較:awk 適合欄位處理,grep 適合行匹配,sed 適合文字替換
  • 學習曲線:awk 實際上是完整的程式語言,本章只介紹最實用的部分
  • 進階學習:掌握 BEGIN(前處理)、END(後處理)、內建變數(NRNF)等概念

12.3.2 欄位處理

背景(問題發現)

許多資料檔案是以空白或特定字元分隔的欄位格式:系統的 /etc/passwd 使用冒號分隔、CSV 檔案使用逗號、log 檔可能使用空白。我們常需要提取其中特定欄位來分析,例如從 log 中取出時間戳記和錯誤訊息,或從使用者列表中提取帳號名稱。手動處理既費時又容易出錯。

方法

awk 的欄位處理機制非常直覺: - 欄位變數$1$2$3 等代表第 1、2、3 欄,$0 代表整行 - 預設分隔符:空白(space 或 tab)會自動作為欄位分隔符 - 自訂分隔符:使用 -F 選項指定(如 -F: 表示用冒號分隔) - 輸出格式:多個欄位用逗號分隔會自動加上空格,直接連接則無空格

結果(程式碼)

# 印出第一欄
awk '{print $1}' file.txt

# 印出第一和第三欄
awk '{print $1, $3}' file.txt

# 使用自訂分隔符
awk -F: '{print $1}' /etc/passwd

討論/延伸

  • 實用範例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 += $1} END {print sum}' file.txt

# 計算平均
awk '{sum += $1; count++} END {print sum/count}' file.txt

討論/延伸

  • 計算特定欄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 整行

結果(程式碼)

# 第二欄大於 100 的行
awk '$2 > 100 {print}' file.txt

# 使用正規表示式
awk '/pattern/ {print $1}' file.txt

討論/延伸

  • 簡化寫法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 筆結果

結果(程式碼)

# 統計 HTTP 狀態碼
cat access.log | awk '{print $9}' | sort | uniq -c | sort -rn

# 找出最多請求的 IP
cat access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -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

# 取得 CSV 第二欄
awk -F, '{print $2}' data.csv

# 計算某欄總和
awk -F, '{sum += $3} END {print sum}' data.csv

12.4.3 歷史命令分析

# 最常用的 10 個命令
history | awk '{print $2}' | sort | uniq -c | sort -rn | head -10

12.5 現代替代品

在日常使用中,可以考慮:

傳統工具 現代替代
grep ripgrep
sed sd
awk miller (mlr)

但理解傳統工具仍然重要,因為它們在任何 Unix 系統上都可用。

12.6 實作練習

  1. 用 grep 搜尋你的設定檔中所有 export 行
  2. 用 sed 批次取代檔案中的字串
  3. 用 awk 分析你的 shell 歷史記錄
Note建議

日常使用 ripgrep,但保持對 grep/sed/awk 的基本理解,在沒有現代工具的伺服器上會很有用。