引言:文本处理的瑞士军刀
在Shell编程与数据处理领域,`awk` 以其卓越的文本解析与处理能力,成为开发者手中的利器。它不仅具备 `grep` 的检索能力和 `sed` 的编辑功能,更内置了一套完整的编程语言体系,尤其擅长处理行列结构的文本数据。深入掌握 `awk`,能极大提升脚本编写的效率与表达能力。
一、核心概念与运行机制
1.1 什么是awk?
`awk` 是一门专为文本处理设计的领域特定语言,其名称源于三位创始人 Alfred Aho、Peter Weinberger 和 Brian Kernighan 的姓氏首字母。
核心特性:
逐行扫描:自动读取输入文件并按行处理
字段自动分割:默认以空格或制表符将每行拆分为多个字段
模式‑动作架构:对符合指定模式的行执行相应的处理动作
1.2 基本执行流程
1. 执行可选的 `BEGIN` 代码块(通常用于初始化)
2. 读取输入的一行文本 → 自动拆分为 `$1`, `$2`, …, `$NF` 等字段
3. 检查是否匹配给定模式 → 若匹配则执行对应动作
4. 重复步骤 2‑3 直至文件结束
5. 执行可选的 `END` 代码块(通常用于输出汇总结果)
二、基础语法与常用格式
2.1 命令行结构
```bash
基本格式
awk 'pattern {action}' filename
多个模式‑动作对
awk 'pattern1 {action1} pattern2 {action2}' filename
指定字段分隔符
awk F'分隔符' 'pattern {action}' filename
```
2.2 入门示例
```bash
打印文件全部内容
awk '{print}' /etc/passwd
打印第1列与第3列(默认空格分隔)
echo e "alice 25\nbob 30" | awk '{print $1, $3}'
添加标题行后输出数据
awk 'BEGIN{print "Name\tAge"} {print $1"\t"$2}' data.txt
```
三、内置变量解析
| 变量 | 说明 | 使用示例 |
| `$0` | 当前整行内容 | `print $0` |
| `$1`~`$n` | 第1至第n个字段 | `print $1, $3` |
| `NF` | 当前行的字段总数 | `$NF` 表示最后一个字段 |
| `NR` | 已读取的总行数(全局) | `NR==1` 匹配第一行 |
| `FNR` | 当前文件的行数(多文件处理时有用) | |
| `FS` | 输入字段分隔符 | `F:` 或 `FS=":"` |
| `RS` | 输入记录(行)分隔符 | 默认为换行符 |
| `OFS` | 输出字段分隔符 | `OFS=","` |
| `ORS` | 输出记录分隔符 | `ORS="\n"` |
| `FILENAME` | 当前处理的文件名 | |
| `ARGC` | 命令行参数个数 | |
| `ARGV` | 命令行参数数组 | `ARGV[0]` 为 awk 自身 |
实践示例:
```bash
输出行号与内容
awk '{print NR, $0}' file.txt
输出每行最后一列
awk '{print $NF}' /etc/passwd
以逗号为分隔符输出前两列
awk 'BEGIN{OFS=","} {print $1, $2}' file.txt
```
四、模式匹配详解
4.1 正则匹配
```bash
匹配含“error”的行(忽略大小写)
awk '/error/i {print}' logfile
匹配以“192.168”开头的行
awk '/^192\.168/ {print}' access.log
匹配第3列大于100的行
awk '$3 > 100 {print}' data.txt
```
4.2 条件表达式
```bash
数值范围筛选
awk '$2 >= 18 && $2 <= 60 {print $1, "Worker"}' ages.txt
字符串精确匹配
awk '$1 == "root" {print $0}' /etc/passwd
多条件组合
awk '$3 > 100 || $4 ~ /fail/ {print NR, $0}' monitor.log
```
4.3 特殊模式:BEGIN 与 END
```bash
BEGIN:处理数据前执行(常用于初始化)
awk 'BEGIN{FS=":"; OFS="\t"; print "User Report"} {print $1, $3}' /etc/passwd
END:处理完所有数据后执行(常用于汇总)
awk '{sum+=$2} END{print "Total:", sum, "Average:", sum/NR}' data.txt
```
五、实战场景应用
5.1 日志分析
```bash
统计各IP访问次数
awk '{ips[$1]++} END{for(ip in ips) print ip, ips[ip]}' access.log | sort k2nr
提取404错误的URL
awk '$9 == 404 {print $7}' access.log | sort | uniq c | sort nr
按小时统计请求量
awk F'[/:]' '{hour[$2":"$3]++} END{for(h in hour) print h, hour[h]}' access.log
```
5.2 系统管理
```bash
列出UID≥1000的普通用户
awk F: '$3 >= 1000 {print $1, $3, $6}' /etc/passwd
筛选CPU占用率>10%的进程
ps aux | awk '$3 > 10 {print $2, $3, $11}'
计算当前目录下文件总大小(MB)
ls l | awk '{sum+=$5} END{print "Total:", sum/1024/1024, "MB"}'
```
5.3 数据加工
```bash
处理CSV:成绩>80则标记为通过
awk F, '{if($3 > 80) print $1, "Pass"}' grades.csv
去重(保留首次出现的行)
awk '!seen[$0]++' file.txt
矩阵转置(行列互换)
awk '{for(i=1; i<=NF; i++) a[i,NR]=$i} END{for(i=1; i<=NF; i++) {for(j=1; j<=NR; j++) printf "%s ", a[i,j]; print ""}}' matrix.txt
```
六、高级功能
6.1 数组应用
```bash
统计单词频率
awk '{for(i=1; i<=NF; i++) freq[$i]++} END{for(w in freq) print w, freq[w]}' words.txt
模拟二维数组统计组合频次
awk '{count[$1","$2]++} END{for(key in count) print key, count[key]}' data.txt
```
6.2 自定义函数
```bash
awk '
function max(a, b) {
return a > b ? a : b
}
{
print "Max of", $1, "and", $2, "is", max($1, $2)
}
' numbers.txt
```
6.3 流程控制
```bash
条件分支
awk '{if($2 > 90) grade="A"; else if($2 > 80) grade="B"; else grade="C"; print $1, grade}' scores.txt
循环求行平均值
awk '{sum=0; for(i=1; i<=NF; i++) sum+=$i; print "Row", NR, "Average:", sum/NF}' data.txt
逐词输出
awk '{i=1; while(i<=NF) {print i, $i; i++}}' file.txt
```
6.4 多文件关联处理
```bash
类似 SQL JOIN:基于 file1 的 $1 匹配 file2 的 $1
awk 'NR==FNR{a[$1]=$2; next} $1 in a{print $0, a[$1]}' file1 file2
```
七、性能优化建议
1. 内置操作优先
避免在 awk 中频繁调用 `system()` 等外部命令,尽量使用 awk 内置函数与语法完成处理。
2. 减少正则匹配开销
若只需对特定字段进行匹配,使用 `$n ~ /pattern/` 而非全行匹配 `/pattern/`。
3. 使用 `next` 跳过无关行
在处理大型文件时,尽早用 `next` 跳过无需处理的行,减少后续判断开销。
4. 预编译正则表达式
在 `BEGIN` 块中定义复杂正则,避免在每行处理时重复编译。
八、常见注意点与良好实践
陷阱规避
Shell变量传递
使用 `v` 选项传递变量,而非在 awk 程序中直接引用 Shell 变量。
```bash
正确方式
var="test"; awk v v="$var" '$0 ~ v {print}' file
```
特殊分隔符转义
使用字符类避免转义问题,例如 `'[|]'` 匹配竖线分隔符。
编码建议:
简短脚本可直接写在命令行中,复杂逻辑建议保存为 `.awk` 文件并通过 `f` 加载。
使用 `printf` 进行格式化输出,便于控制对齐、精度等样式。
适当添加注释,提高脚本可读性。
九、综合案例:Nginx访问日志分析
以下脚本统计各IP的请求次数、总流量与平均响应时间:
```bash
!/bin/bash
awk '
BEGIN {
FS = "\"" 以双引号为分隔符便于提取字段
OFS = "\t"
print "IP\t\tRequests\tTotalBytes\tAvgTime(ms)"
}
{
ip = $1
split($3, a, " ") 从请求行中提取响应大小
bytes = a[2]
time = $4
req[ip]++
bytes_sum[ip] += bytes
time_sum[ip] += time
}
END {
for(ip in req) {
avg_time = time_sum[ip] / req[ip]
printf "%15s %8d %12d %12.2f\n",
ip, req[ip], bytes_sum[ip], avg_time
}
}
' access.log | sort k2nr
```
十、学习路径与核心思想
循序渐进掌握:
1. 入门:掌握 `print`、字段引用 `$n`、内置变量
2. 进阶:熟悉模式匹配、正则表达式、`BEGIN`/`END` 块
3. 熟练:运用数组、流程控制、自定义函数
4. 精通:多文件处理、性能调优、复杂脚本设计
核心思维:
数据驱动:始终围绕“匹配什么数据”和“如何操作数据”展开思考
内置优先:尽可能在 awk 内部完成所有处理,减少与 Shell 的交互开销
字段中心:一切操作围绕 `$1`, `$2`, …, `NF` 等字段展开
作为一门融合命令行工具灵活性与编程语言表达力的文本处理语言,`awk` 在数据提取、转换、统计等场景中表现出色。建议在接下来的文本处理任务中尝试使用 `awk`,实践将帮助你更快掌握其精髓。
来源:小程序app开发|ui设计|软件外包|IT技术服务公司-木风未来科技-成都木风未来科技有限公司