Linux awk 命令:文本处理的瑞士军刀

4 阅读2分钟

awk 是 Linux 下最强大的文本处理工具之一,名字取自三位创始人 Aho、Weinberger、Kernighan 的姓氏首字母。很多人只用它做简单的列提取,其实 awk 的能力远不止于此。

awk 的核心模型

awk 的工作流程可以概括为:

awk 'pattern { action }' file
  • pattern:匹配条件(正则、表达式、范围)
  • action:执行的操作(打印、计算、变量赋值)

对于每一行,awk 会:

  1. 自动按分隔符分割字段(默认空格)
  2. 字段存入 $1, $2, $3...,整行是 $0
  3. 检查 pattern,匹配则执行 action
# 提取第一列和第三列
awk '{ print $1, $3 }' data.txt

# 只处理包含 "error" 的行
awk '/error/ { print $0 }' app.log

# 计算文件总行数
awk 'END { print NR }' data.txt

NR 是内置变量,表示当前行号(Number of Records)。END 是特殊模式,在所有行处理完后执行。

字段分隔符:不只是空格

-F 参数指定字段分隔符:

# CSV 文件按逗号分割
awk -F',' '{ print $1, $3 }' data.csv

# 使用正则表达式:一个或多个空格
awk -F'[ ]+' '{ print $1 }' data.txt

# 多字符分隔符
awk -F'|' '{ print $1 }' data.txt

也可以在脚本内设置 FS(Field Separator):

awk 'BEGIN { FS = "," } { print $1, $3 }' data.csv

BEGIN 在处理任何行之前执行,常用于初始化变量。

内置变量的秘密

awk 提供了多个内置变量:

变量含义
$0整行内容
$1~$n第 n 个字段
NF当前行字段数(Number of Fields)
NR当前行号(全局)
FNR当前行号(当前文件)
FS字段分隔符
OFS输出字段分隔符
RS行分隔符
ORS输出行分隔符

NF 的妙用:引用最后一个字段

# 打印每行的最后一个字段
awk '{ print $NF }' data.txt

# 打印倒数第二个字段
awk '{ print $(NF-1) }' data.txt

条件判断与循环

awk 支持 if-elsefor/while 循环:

# 按条件过滤并标记
awk '{
  if ($3 > 100) {
    print $1, "HIGH"
  } else {
    print $1, "NORMAL"
  }
}' data.txt

# 计算每行的字段和
awk '{
  sum = 0
  for (i = 1; i <= NF; i++) {
    sum += $i
  }
  print sum
}' numbers.txt

数组与统计

awk 的数组是关联数组(associative array),键可以是任意字符串:

# 统计每个单词出现次数
awk '{
  for (i = 1; i <= NF; i++) {
    count[$i]++
  }
}
END {
  for (word in count) {
    print word, count[word]
  }
}' text.txt

# 按访问量统计 HTTP 状态码
awk '{ count[$9]++ } END { for (code in count) print code, count[code] }' access.log

这段代码中 $9 是 Nginx 日志的状态码字段(假设标准格式)。

实战案例:分析 Nginx 访问日志

假设日志格式:

192.168.1.1 - - [10/May/2026:10:30:45 +0800] "GET /api/users HTTP/1.1" 200 1234 "-" "Mozilla/5.0"

1. 统计 Top 10 访问 IP

awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -10

纯 awk 实现:

awk '{
  ip[$1]++
}
END {
  for (i in ip) print ip[i], i
}' access.log | sort -rn | head -10

2. 计算平均响应时间

假设日志格式包含响应时间(最后一个字段):

awk '{
  total += $NF
  count++
}
END {
  print "Average:", total/count, "ms"
}' access.log

3. 提取 4xx 和 5xx 错误

# 提取所有 4xx 和 5xx 状态码的请求
awk '$9 ~ /^[45][0-9][0-9]$/ { print $0 }' access.log

# 统计错误类型分布
awk '$9 ~ /^[45][0-9][0-9]$/ {
  errors[$9]++
}
END {
  for (code in errors) print code, errors[code]
}' access.log

~ 是正则匹配操作符,$9 ~ /^.../ 表示第 9 个字段匹配正则。

性能优化技巧

1. 跳过无效行

next 跳过不需要处理的行:

awk '/^#/ { next } { print $1 }' config.conf

跳过注释行(以 # 开头)。

2. 只处理前 N 行

awk 'NR > 100 { exit } { print $1 }' data.txt

处理前 100 行后退出,避免读取整个大文件。

3. 多文件处理时的 FNR

当处理多个文件时,NR 是全局行号,FNR 是当前文件行号:

# 每个文件单独统计
awk 'FNR == 1 { print "File:", FILENAME } { print NR, FNR, $0 }' file1.txt file2.txt

复杂案例:计算移动平均

假设有一个温度数据文件,每行一个温度值,计算 3 点移动平均:

awk '{
  values[NR] = $1
  if (NR >= 3) {
    sum = values[NR] + values[NR-1] + values[NR-2]
    print (NR-2), sum/3
  }
}' temperature.txt

awk vs sed vs grep

很多人分不清这三个工具的边界:

工具核心能力典型场景
grep行过滤快速搜索匹配行
sed流编辑替换、删除、插入
awk字段处理 + 计算统计、报表、格式化

三者常组合使用:

# 组合示例:提取 error 行,替换时间戳格式,统计按小时分布
grep "ERROR" app.log | \
  sed 's/\[.*\]//' | \
  awk '{ count[$1]++ } END { for (h in count) print h, count[h] }'

小结

awk 的强大在于:

  1. 自动字段分割,省去手动 split
  2. 完整的编程语言(变量、数组、函数、循环)
  3. 内置的模式匹配机制

掌握 awk,处理文本文件就像用 SQL 查询数据库一样高效。复杂的统计、格式化、转换任务,一行 awk 命令就能搞定。


相关工具:Linux sed 命令 | 文本去重工具 | Grep 命令详解