Nginx access.log 以時間戳記錄每筆請求。需求:提取 2010-08-16 14:31:30 至 2010-08-17 10:12:07 區間的所有日誌行,寫入 log.txt。
#!/bin/bash
# 取得起始行號(首次出現)
n1=$(grep -n '16\/Aug\/2010:14:31:30' access.log | head -1 | cut -d: -f1)
# 取得結束行號(最後出現)
n2=$(grep -n '17\/Aug\/2010:10:12:07' access.log | tail -1 | cut -d: -f1)
# 邊界檢查
if [[ -z "$n1" || -z "$n2" ]]; then
echo "時間戳未找到,請確認日誌格式"
exit 1
fi
# sed 按行號範圍輸出
sed -n "${n1},${n2}p" access.log > log.txt
echo "已提取 $((n2-n1+1)) 行至 log.txt"
sed -n '/16\/Aug\/2010:14:31:30/,/17\/Aug\/2010:10:12:07/p' access.log > log.txt
缺陷:若同一秒內有大量並發日誌(同時間戳重複),sed 會在首次匹配起始模式到首次匹配結束模式之間輸出,導致漏掉同時間戳後續行或提前結束。
awk '
# 將 Nginx 時間轉為 epoch 秒
function to_epoch(t) {
gsub(/[\[\]\/:/, " ", t)
split(t, a, " ")
mon["Jan"]=1; mon["Feb"]=2; mon["Mar"]=3; mon["Apr"]=4; mon["May"]=5; mon["Jun"]=6
mon["Jul"]=7; mon["Aug"]=8; mon["Sep"]=9; mon["Oct"]=10; mon["Nov"]=11; mon["Dec"]=12
return mktime(a[4]" "mon[a[3]]" "a[2]" "a[5]" "a[6]" "a[7])
}
BEGIN {
start=to_epoch("16/Aug/2010:14:31:30")
end=to_epoch("17/Aug/2010:10:12:07")
}
{
# 假設 $4 為 [$time_local] 格式
match($4, /\[(.*)\]/, arr)
if (arr[1] != "") {
t=to_epoch(arr[1])
if (t >= start && t <= end) print
}
}' access.log > log.txt
logrotate 按天/大小切割,避免單檔過大導致 grep/sed 效能下降。對於單機、中小規模日誌,**方案一(行號定位)最佳平衡了精準度與實作簡易度**。核心原則:先定位物理行號,再按範圍輸出,規避邏輯匹配的邊界陷阱。