文本处理(上):awk
awk 核心定位
awk 是按列处理文本的利器。它把每一行按分隔符切分成多个字段(Field),然后对指定字段进行操作。
基础语法
awk 'pattern { action }' file
- pattern:匹配条件(可选)
- action:对匹配行执行的操作
- file:输入文件
# 打印文件的每一行(等同于 cat)
awk '{print}' file.txt
# 打印第 1 列
awk '{print $1}' file.txt
# 打印第 1 列和第 3 列
awk '{print $1, $3}' file.txt
# 打印整行($0 表示整行)
awk '{print $0}' file.txt
# 打印行号(NR = Number of Record)
awk '{print NR, $0}' file.txt
# 打印总行数
awk 'END {print NR}' file.txt
分隔符处理
# 指定分隔符(-F field-separator)
awk -F: '{print $1}' /etc/passwd # 以 : 分隔,打印用户名
awk -F, '{print $2}' data.csv # 以 , 分隔,打印第 2 列
# 多个分隔符
awk -F'[:,]' '{print $1}' file.txt # 以 : 或 , 分隔
# 正则分隔符
awk -F'[ ]+' '{print $1}' file.txt # 一个或多个空格
/etc/passwd 结构示例
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
| 字段 | 含义 |
|---|---|
| $1 | 用户名 |
| $2 | 密码占位符(x) |
| $3 | UID |
| $4 | GID |
| $5 | 用户全名/注释 |
| $6 | 家目录 |
| $7 | 默认 Shell |
# 提取用户名和 Shell
awk -F: '{print "User:", $1, "Shell:", $7}' /etc/passwd
# 输出:
# User: root Shell: /bin/bash
# User: daemon Shell: /usr/sbin/nologin
条件过滤
# 打印 UID >= 1000 的普通用户
awk -F: '$3 >= 1000 {print $1, $3}' /etc/passwd
# 打印 Shell 是 /bin/bash 的用户
awk -F: '$7 == "/bin/bash" {print $1}' /etc/passwd
# 打印包含 "root" 的行
awk '/root/' /etc/passwd
# 打印第 3 列大于 50 的行
awk '$3 > 50 {print $1, $3}' data.txt
# 匹配正则表达式
awk '$1 ~ /^[A-Z]/ {print $1}' file.txt # 第 1 列以大写字母开头
内置变量
| 变量 | 含义 | 示例 |
|---|---|---|
$0 | 整行内容 | awk '{print $0}' |
$1-$n | 第 n 个字段 | awk '{print $1}' |
NF | 字段数量(Number of Fields) | awk '{print NF}' |
NR | 行号(Number of Record) | awk '{print NR, $0}' |
FS | 输入分隔符(Field Separator) | awk 'BEGIN{FS=":"}' |
OFS | 输出分隔符(Output FS) | awk 'BEGIN{OFS=","}' |
RS | 记录分隔符(Record Separator,默认换行) | awk 'BEGIN{RS=""}' |
ORS | 输出记录分隔符 | awk 'BEGIN{ORS="\n\n"}' |
FILENAME | 当前文件名 | awk '{print FILENAME}' |
# 打印每行的字段数
awk '{print NR, NF, $0}' file.txt
# 打印最后一列(用 NF)
awk '{print $NF}' file.txt
# 自定义输出格式
awk 'BEGIN{OFS=" | "} {print $1, $2, $3}' file.txt
BEGIN 和 END 块
# BEGIN:处理文件前执行
# END:处理完所有行后执行
# 统计文件行数、单词数、字符数(简化版 wc)
awk 'BEGIN{print "开始统计..."}
{lines++; words+=NF; chars+=length($0)}
END{print "行数:", lines
print "单词数:", words
print "字符数:", chars}' file.txt
# 计算第 2 列的平均值
awk '{sum+=$2; count++} END{print "平均值:", sum/count}' data.txt
# 找出第 3 列的最大值
awk 'NR==1{max=$3} $3>max{max=$3} END{print "最大值:", max}' data.txt
实战场景
场景 1:分析 Nginx 访问日志
# 日志格式:IP - - [时间] "请求方法 URL 协议" 状态码 大小 "Referer" "User-Agent"
# 示例:192.168.1.1 - - [15/Jan/2024:10:00:00 +0800] "GET /index.html HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
# 提取 IP 和状态码
awk '{print $1, $9}' access.log
# 统计每个 IP 的访问次数
awk '{count[$1]++} END{for(ip in count) print count[ip], ip}' access.log | sort -rn
# 统计每个状态码的数量
awk '{code[$9]++} END{for(c in code) print code[c], c}' access.log
# 计算平均响应大小(第 10 列是响应大小)
awk '{sum+=$10; count++} END{print "平均大小:", sum/count, "字节"}' access.log
场景 2:处理 CSV 文件
# data.csv:
# name,age,salary
# Alice,25,8000
# Bob,30,12000
# Carol,28,9500
# 打印姓名和工资
awk -F, 'NR>1 {print $1, $3}' data.csv
# 计算平均工资(跳过标题行)
awk -F, 'NR>1 {sum+=$3; count++} END{print "平均工资:", sum/count}' data.csv
# 找出工资最高的人
awk -F, 'NR>1 && $3>max{max=$3; name=$1} END{print "最高工资:", name, max}' data.csv
本篇小结
awk是按列处理文本的核心工具,默认以空格/制表符分隔- 基础语法:
awk 'pattern {action}' file -F指定分隔符,-F:以冒号分隔,-F,以逗号分隔$0整行,$1-$n第 n 列,$NF最后一列NR行号,NF字段数,FS/OFS输入/输出分隔符BEGIN块:处理前初始化;END块:处理后统计输出- 数组统计:
count[$1]++,END{for(x in count) print count[x], x}
动手实践
解析 /etc/passwd:
awk -F: '{print "用户:", $1, "UID:", $3, "Shell:", $7}' /etc/passwd awk -F: '$3 >= 1000 {print $1, $3}' /etc/passwd创建测试数据并处理:
cat > scores.txt Alice 85 90 78 Bob 92 88 95 Carol 78 82 80 # Ctrl+D # 计算每人总分和平均 awk '{sum=$2+$3+$4; avg=sum/3; print $1, "总分:", sum, "平均:", avg}' scores.txt # 找出最高分 awk '{sum=$2+$3+$4; if(sum>max){max=sum; name=$1}} END{print "最高分:", name, max}' scores.txt统计单词频率:
awk '{for(i=1;i<=NF;i++) count[$i]++} END{for(w in count) print count[w], w}' scores.txt | sort -rn处理 CSV:
cat > shop.csv item,price,quantity apple,5.5,100 banana,3.2,200 orange,4.8,150 # Ctrl+D awk -F, 'NR>1 {print $1, "总价:", $2*$3}' shop.csv awk -F, 'NR>1 {total+=$2*$3} END{print "总销售额:", total}' shop.csv思考:
awk '{print $NF}'和awk '{print $(NF-1)}分别输出什么?如果一行有 5 个字段,NF的值是多少?