Skip to main content

Unix/Linux 的 awk 指令使用(上)

要寫 awk 教學時其實我也是邊寫邊學,但是發現這個指令已經發展的太過龐大不是一篇簡單的文章就能清楚表達,於是求助 AI。比較網路文章和 AI 生成的文章,我個人認為內容比網路上九成的教學都更清晰,所以毫不客氣的給 AI 寫並且人工潤飾+校稿,本文使用此 prompt 給 Claude 3.7 生成:

prompt

撰寫awk指令教學,務必由淺入深,循序漸進的說明,從簡單常用的功能開始介紹,再進入到中階和高階用法。遇到新的語法必須詳細說明符號用途。 此教學要求以下組成:

  1. 每次對話的主標題是h2,每個內容是h3,每個h2標題使用emoji: 📘,其他標題不可有emoji,內容使用繁體中文
  2. 絕對禁止使用h4標題,也就是說嚴厲禁止出現4個#開頭的內容禁止使用獨立段落介紹指令範例,必須和參數一同出現同時介紹
  3. 教學資訊密度高,內容緊湊,確保類似功能在同一個上下文,範例指令簡潔易懂,輸出精簡,用法符合工程實務。

使用此nginx log.txt作為範例文本,之後所有內容全部基於此文本測試

192.168.0.101 - - [17/May/2025:21:14:12 +0000] "GET / HTTP/1.1" 200 1536 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"

192.168.0.101 - - [17/May/2025:21:16:44 +0000] "GET /dashboard HTTP/1.1" 200 4721 "https://example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X)" "-"

203.0.113.45 - john [17/May/2025:21:15:30 +0000] "POST /login HTTP/1.1" 302 423 "https://example.com/login" "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6)" "198.51.100.23"

10.1.2.3 - - [17/May/2025:21:17:10 +0000] "GET /assets/css/style.css HTTP/1.1" 304 0 "https://example.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"

172.16.4.5 - alice [17/May/2025:21:18:57 +0000] "DELETE /api/user/321 HTTP/1.1" 403 732 "-" "PostmanRuntime/7.30.0" "172.16.4.5"

203.0.113.45 - john [18/May/2025:09:01:11 +0000] "POST /api/upload HTTP/1.1" 201 9832 "https://example.com/upload" "curl/7.68.0" "-"

文章編排有基礎(800字)、中階1(1000字)、中階2(1000字)、高階(800字)四個篇章,內容長度800字,內容以code block顯示,不要放在對話框,基礎篇只能包含最簡易使用,任何條件判斷和變數都留到中階篇介紹。

先生成段落規劃讓我檢查是否符合要求。

完整的 log.txt

使用這份 log.txt 可以測試更長文件的執行結果。

192.168.0.101 - - [17/May/2025:21:14:12 +0000] "GET / HTTP/1.1" 200 1536 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"
192.168.0.101 - - [17/May/2025:21:16:44 +0000] "GET /dashboard HTTP/1.1" 200 4721 "https://example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X)" "-"
203.0.113.45 - john [17/May/2025:21:15:30 +0000] "POST /login HTTP/1.1" 302 423 "https://example.com/login" "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6)" "198.51.100.23"
10.1.2.3 - - [17/May/2025:21:17:10 +0000] "GET /assets/css/style.css HTTP/1.1" 304 0 "https://example.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"
172.16.4.5 - alice [17/May/2025:21:18:57 +0000] "DELETE /api/user/321 HTTP/1.1" 403 732 "-" "PostmanRuntime/7.30.0" "172.16.4.5"
203.0.113.45 - john [18/May/2025:09:01:11 +0000] "POST /api/upload HTTP/1.1" 201 9832 "https://example.com/upload" "curl/7.68.0" "-"
10.1.2.3 - - [18/May/2025:09:02:28 +0000] "GET /favicon.ico HTTP/1.1" 404 209 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64)" "-"
198.18.0.10 - - [18/May/2025:09:03:33 +0000] "PUT /api/item/789 HTTP/1.1" 500 1177 "https://admin.example.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1)" "-"
192.168.0.101 - root [18/May/2025:09:04:48 +0000] "GET /admin HTTP/1.1" 401 512 "-" "Wget/1.21.1 (linux-gnu)" "127.0.0.1"
145.23.65.8 - - [18/May/2025:09:05:59 +0000] "GET /search?q=test HTTP/2.0" 200 2981 "https://example.com/search" "Mozilla/5.0 (X11; Linux x86_64)" "-"
203.0.113.45 - - [18/May/2025:09:07:10 +0000] "PATCH /api/settings HTTP/1.1" 204 0 "https://example.com/settings" "Mozilla/5.0 (iPad; CPU OS 14_6 like Mac OS X)" "-"
10.1.2.3 - - [18/May/2025:09:08:25 +0000] "OPTIONS /api/data HTTP/1.1" 204 0 "-" "Mozilla/5.0 (Android 12; Mobile)" "-"
192.168.0.101 - - [19/May/2025:10:15:03 +0000] "GET /products HTTP/1.1" 200 3874 "https://example.com/home" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"
203.0.113.45 - john [19/May/2025:10:16:45 +0000] "POST /checkout HTTP/1.1" 302 541 "https://example.com/cart" "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6)" "-"
172.16.4.5 - alice [19/May/2025:10:18:01 +0000] "DELETE /api/session HTTP/1.1" 403 691 "-" "PostmanRuntime/7.30.0" "-"
198.18.0.10 - - [19/May/2025:10:20:15 +0000] "PUT /api/item/222 HTTP/1.1" 500 1093 "https://admin.example.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1)" "-"
203.0.113.45 - john [19/May/2025:10:21:33 +0000] "POST /logout HTTP/1.1" 200 212 "https://example.com/profile" "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6)" "-"
10.1.2.3 - - [19/May/2025:10:22:47 +0000] "GET /assets/js/app.js HTTP/1.1" 304 0 "https://example.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"
192.168.0.101 - - [19/May/2025:10:23:59 +0000] "GET /about HTTP/1.1" 200 1432 "https://example.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"
145.23.65.8 - - [19/May/2025:10:25:03 +0000] "HEAD /ping HTTP/1.1" 200 0 "-" "curl/7.79.1" "-"
198.51.100.23 - - [19/May/2025:10:26:44 +0000] "GET /blog HTTP/1.1" 200 6783 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64)" "-"
172.16.4.5 - alice [19/May/2025:10:27:55 +0000] "POST /api/comment HTTP/1.1" 201 998 "https://example.com/post/123" "PostmanRuntime/7.30.0" "-"
192.168.0.101 - root [19/May/2025:10:29:12 +0000] "GET /admin/logs HTTP/1.1" 403 1243 "-" "Wget/1.21.1 (linux-gnu)" "127.0.0.1"
10.1.2.3 - - [19/May/2025:10:30:38 +0000] "GET /robots.txt HTTP/1.1" 200 68 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64)" "-"
203.0.113.45 - - [19/May/2025:10:32:17 +0000] "PUT /api/profile HTTP/1.1" 200 1543 "https://example.com/settings" "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)" "-"
198.18.0.10 - - [19/May/2025:10:34:05 +0000] "DELETE /api/item/333 HTTP/1.1" 404 213 "https://admin.example.com/items" "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1)" "-"

範例內容使用 Nginx log 比較符合現實世界,有文章拿 csv 作範例超奇怪,都已經用 csv 了為什麼不用專門工具 (mlr, qsv, csvkit) 偏偏要用 awk 來虐待自己,而且用 awk 解析 csv 要求任何欄位都不可出現特殊字符,一出現直接代表解析錯誤。前言結束,以下開始 AI 的表演。

📘 AWK 基礎篇

如果有指令不能執行,請安裝 gnu 版本的 awk,macos 請使用 brew install gawk 安裝並改為 gawk。

AWK 簡介與基本概念

AWK 是一種強大的文本處理語言,特別適合用於結構化文本的處理與分析。它的名稱來自於三位創始人 Alfred Aho、Peter Weinberger 和 Brian Kernighan 的姓氏首字母。AWK 的核心思想是將文本檔案視為行和欄位,並提供簡便的方式對這些數據進行操作。

提示

awk 的基礎邏輯是每次處理一行,每行視為多個不同欄位,欄位之間使用分隔符 (delimiter) 分開,預設是空隔。

學習 awk 時要記得他很大程度上參考了 C 語言,有了這個前提後閱讀中階篇就會非常輕鬆。

基本語法結構

AWK 命令的基本結構如下:

awk '模式 {動作}' 檔案名稱

其中:

  • 模式:決定何時執行動作,可以是條件、正則表達式或特殊模式
  • 動作:當模式匹配時要執行的操作,通常包含在大括號內
  • 檔案名稱:要處理的檔案

基本用法範例,印出所有行:

awk '{print}' log.txt

欄位處理

AWK 最強大的功能之一是處理文本的欄位。默認情況下,AWK 使用空白(空格、tab)作為欄位分隔符。

  • $0:代表整行內容
  • $1:第一個欄位
  • $2:第二個欄位
  • $NF:最後一個欄位

使用範例,印出日誌檔的 IP 地址和時間:

awk '{print $1, $6}' log.txt

輸出為:

192.168.0.101 [17/May/2025:21:14:12
192.168.0.101 [17/May/2025:21:16:44
203.0.113.45 [17/May/2025:21:15:30
...

移除前三欄:

# 管道符後的 sed 用於移除空白
awk '{$1=$2=$3=""; print $0}' log.txt | sed 's/^ *//'

輸出為:

[17/May/2025:21:14:12 +0000] "GET / HTTP/1.1" 200 1536 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"
[17/May/2025:21:16:44 +0000] "GET /dashboard HTTP/1.1" 200 4721 "https://example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X)" "-"
[17/May/2025:21:15:30 +0000] "POST /login HTTP/1.1" 302 423 "https://example.com/login" "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6)" "198.51.100.23"
...

修改欄位分隔符:

awk -F"[: ]" '{print NF}' log.txt

-F"[: ]" 指定以冒號和空格作為分隔符,印出 NF 查看分隔後的欄位數量。

內建變數

AWK 提供了許多內建變數,使文本處理更加便捷:

awk 的內建變數
  • NR:當前處理的記錄(行)編號
  • NF:當前記錄的欄位數量
  • FS:輸入欄位分隔符(默認為空白)
  • OFS:輸出欄位分隔符(默認為空格)
  • RS:輸入記錄分隔符(默認為換行符)
  • ORS:輸出記錄分隔符(默認為換行符)

帶行號顯示日誌:

awk '{print NR, $0}' log.txt

輸出:

1 192.168.0.101 - - [17/May/2025:21:14:12 +0000] "GET / HTTP/1.1" 200 1536 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"
2 192.168.0.101 - - [17/May/2025:21:16:44 +0000] "GET /dashboard HTTP/1.1" 200 4721 "https://example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X)" "-"
...

顯示每行欄位數:

awk '{print NF}' log.txt

使用 NF 印出最後一個欄位:

awk '{print $NF}' log.txt

簡單的輸出格式化

AWK 提供兩種主要的輸出命令:printprintf

print 簡單易用,會自動在每個輸出後添加換行符:

awk '{print "IP:", $1, "狀態碼:", $9}' log.txt

輸出:

IP: 192.168.0.101 狀態碼: 200
IP: 192.168.0.101 狀態碼: 200
IP: 203.0.113.45 狀態碼: 302
IP: 10.1.2.3 狀態碼: 304
IP: 172.16.4.5 狀態碼: 403
IP: 203.0.113.45 狀態碼: 201

printf 提供更精確的格式控制,類似於 C 語言的 printf:

awk '{printf "%-15s %s\n", $1, $9}' log.txt

輸出:

192.168.0.101   200
192.168.0.101 200
203.0.113.45 302
10.1.2.3 304
172.16.4.5 403
203.0.113.45 201

其中:

  • %-15s:左對齊的 15 字元寬度的字串
  • %s:字串
  • \n:換行(使用 printf 時需要手動添加換行符)

以表格形式顯示 IP 和 HTTP 狀態:

awk 'BEGIN {printf "%-15s %s\n", "IP", "狀態碼"} {printf "%-15s %s\n", $1, $9}' log.txt

輸出:

IP             狀態碼
192.168.0.101 200
192.168.0.101 200
203.0.113.45 302
10.1.2.3 304
172.16.4.5 403
203.0.113.45 201

這些基本功能已經能夠讓您在日常工作中處理大多數的文本格式化和數據提取任務。

📘 AWK 中階篇 1

文本過濾

AWK 強大之處在於能夠根據特定條件過濾文本。過濾是透過在 AWK 命令中指定模式來實現的。只有當模式匹配成功時,相應的操作才會執行。

印出狀態碼為 200 的請求:

awk '$9 == "200" {print $0}' log.txt

輸出:

192.168.0.101 - - [17/May/2025:21:14:12 +0000] "GET / HTTP/1.1" 200 1536 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"
192.168.0.101 - - [17/May/2025:21:16:44 +0000] "GET /dashboard HTTP/1.1" 200 4721 "https://example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X)" "-"

印出 HTTP 方法為 POST 的請求(注意這裡 $6 包含雙引號):

awk '$6 == "\"POST" {print $1, $6, $7}' log.txt

輸出:

203.0.113.45 "POST /login
203.0.113.45 "POST /api/upload

模式匹配進階 (正則表達式)

AWK 支援使用正則表達式進行更複雜的匹配。使用 /正則表達式/ 語法可以匹配文本。

常用的正則表達式符號
  • .:匹配任何單個字符
  • *:匹配前一個字符零次或多次
  • +:匹配前一個字符一次或多次
  • ?:匹配前一個字符零次或一次
  • ^:匹配行首
  • $:匹配行尾
  • []:匹配字符集中的任何字符
  • ():分組
  • |:或操作

完整列表請見正則表達式 cheatsheet

找出所有使用 Windows,且包含版本號碼的請求:

awk '/Windows.*[0-9]{1,}/ {print $0}' log.txt

輸出:

192.168.0.101 - - [17/May/2025:21:14:12 +0000] "GET / HTTP/1.1" 200 1536 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"
10.1.2.3 - - [17/May/2025:21:17:10 +0000] "GET /assets/css/style.css HTTP/1.1" 304 0 "https://example.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "-"

組合使用正則表達式和欄位條件:

awk '/GET/ && $9 == "304" {print "緩存命中:", $7}' log.txt

輸出:

緩存命中: /assets/css/style.css

匹配特定日期的請求:

awk '$4 ~ /18\/May\/2025/ {print "日期:", $4, "IP:", $1}' log.txt

輸出:

日期: [18/May/2025:09:01:11 IP: 203.0.113.45

其中,~ 運算符用於正則表達式匹配,!~ 運算符用於正則表達式不匹配。

BEGIN 與 END 區塊

AWK 提供了兩種特殊模式:BEGIN 和 END。

  • BEGIN {動作}:在處理任何輸入行之前執行
  • END {動作}:在處理所有輸入行之後執行

這些區塊特別適合用於初始化變數和輸出結果摘要。

顯示請求統計:

awk 'BEGIN {print "開始分析日誌..."; count=0} $9 == "200" {count++} END {print "共有", count, "個成功請求"}' log.txt

輸出:

開始分析日誌...
共有 2 個成功請求

用於生成表頭和表尾的報告:

awk 'BEGIN {print "IP地址\t\t狀態碼\tURI"} {printf "%s\t%s\t%s\n", $1, $9, $7} END {print "--- 日誌分析完成 ---"}' log.txt

條件判斷結構 (if-else)

AWK 支援 if-else 條件結構,例如可以根據狀態碼分類請求:

awk '{
if ($9 >= 200 && $9 < 300) {
print $1, "成功請求:", $9
} else if ($9 >= 300 && $9 < 400) {
print $1, "重定向:", $9
} else if ($9 >= 400 && $9 < 500) {
print $1, "客戶端錯誤:", $9
}
}' log.txt

輸出:

192.168.0.101 成功請求: 200
192.168.0.101 成功請求: 200
203.0.113.45 重定向: 302
10.1.2.3 重定向: 304
172.16.4.5 客戶端錯誤: 403
203.0.113.45 成功請求: 201

使用 if 判斷特定欄位:

awk '{
method = $6
gsub(/\"/, "", method) # 去除引號
if (method == "GET") {
print "GET 請求:", $7
} else if (method == "POST") {
print "POST 請求:", $7
} else if (method == "DELETE") {
print "刪除請求:", $7
}
}' log.txt

迴圈結構 (for, while)

AWK 支援使用 for 和 while 迴圈進行重複操作:

for 迴圈基本語法:

for (初始化; 條件; 增量) {
動作
}

while 迴圈基本語法:

while (條件) {
動作
}

處理每行的每個欄位:

awk '{
print "行 " NR " 有 " NF " 個欄位:"
for (i = 1; i <= 3; i++) {
print " 欄位 " i ": " $i
}
}' log.txt

使用 while 迴圈:

awk '{
i = 1
while (i <= 3) {
print "行 " NR ", 欄位 " i ": " $i
i++
}
}' log.txt

印出第四欄之後的內容:

awk '{
for (i = 4; i <= NF; i++) { printf("%s", $i) }
printf("\n")
}' log.txt

變數使用與操作

AWK 中的變數不需要預先聲明,可以直接使用。變數名稱區分大小寫。

基本變數賦值:

awk '{
total_bytes = $10
request_path = $7
print "路徑:", request_path, "大小:", total_bytes, "位元組"
}' log.txt

輸出:

路徑: / 大小: 1536 位元組
路徑: /dashboard 大小: 4721 位元組
路徑: /login 大小: 423 位元組
路徑: /assets/css/style.css 大小: 0 位元組
路徑: /api/user/321 大小: 732 位元組
路徑: /api/upload 大小: 9832 位元組

累加計算總位元組:

awk 'BEGIN {total = 0} {total += $10} END {print "總傳輸量:", total, "位元組"}' log.txt

輸出:

總傳輸量: 17244 位元組

字串連接:

awk '{
date_str = $4
ip_addr = $1
full_info = ip_addr " 在 " date_str " 訪問了網站"
print full_info
}' log.txt

輸出:

192.168.0.101 在 [17/May/2025:21:14:12 訪問了網站
192.168.0.101 在 [17/May/2025:21:16:44 訪問了網站
203.0.113.45 在 [17/May/2025:21:15:30 訪問了網站
10.1.2.3 在 [17/May/2025:21:17:10 訪問了網站
172.16.4.5 在 [17/May/2025:21:18:57 訪問了網站
203.0.113.45 在 [18/May/2025:09:01:11 訪問了網站

變數使用於條件中:

awk '{
status = $9
size = $10
if (status == "200" && size > 1000) {
print "大型成功響應:", $7, size, "位元組"
} else if (status == "304") {
print "緩存命中:", $7
}
}' log.txt

輸出:

大型成功響應: / 1536 位元組
大型成功響應: /dashboard 4721 位元組
緩存命中: /assets/css/style.css
大型成功響應: /api/upload 9832 位元組

AWK 與管道連用

AWK 可以與 Unix/Linux 管道配合,實現強大的文本處理流程。管道操作符 | 可以將一個命令的輸出作為另一個命令的輸入。

搭配 grep 預過濾日誌:

grep "GET" log.txt | awk '{print $1, $9, $7}'

輸出:

192.168.0.101 200 /
192.168.0.101 200 /dashboard
10.1.2.3 304 /assets/css/style.css

先用 awk 處理,再用 sort 排序:

awk '$9 >= 200 {print $9, $7}' log.txt | sort -n

輸出:

403 /api/user/321

計算每個 IP 地址的請求次數並排序:

awk '{print $1}' log.txt | sort | uniq -c | sort -nr

輸出:

   2 203.0.113.45
2 192.168.0.101
1 172.16.4.5
1 10.1.2.3

AWK 輸出到管道,再用 awk 二次處理:

awk '{print $1, $9}' log.txt | awk '$2 >= 300 {print "狀態碼大於等於300的IP:", $1}'

輸出:

狀態碼大於等於300的IP: 10.1.2.3
狀態碼大於等於300的IP: 172.16.4.5