awk —— 按字段处理文本
一句话定义
awk 是个小型脚本语言,但 99% 的运维场景只用它的一个特性:把每行按空白拆成字段,操作字段。
ps aux | awk '{print $2, $11}' # 打 PID 和命令
df -h | awk '$5+0 > 80 {print $0}' # 用量超 80% 的文件系统
echo "a b c" | awk '{print $2}' # 输出 "b"
记住一句话:$1 $2 $3 是字段,$0 是整行,NF 是字段数。
典型场景
- 提取列:
kubectl get pods | awk '{print $1}' - 按列过滤:
df -h | awk '$5+0 > 80' - 求和 / 计数:
awk '{sum+=$3} END {print sum}' - 改 CSV:
awk -F, '{...}' - 配合 sed/grep 后的最后一道处理
三种使用形态
1. 单行内嵌(99% 用法)
ps aux | awk '{print $2}'
{...} 里写一段 awk 代码,对每行执行。
2. 带模式
awk '/pattern/ {action}' file
ps aux | awk '/nginx/ {print $2}' # 含 "nginx" 的行打 PID
3. 写到文件
awk -f script.awk input
日常很少这么用——能写到文件的逻辑早该用 Python 了。
awk 的执行模型(最小够用版)
┌──────────┐
input ─►│ BEGIN { } │ (所有行之前跑一次)
├──────────┤
│ pattern { action } │ (对每行:匹配 pattern → 跑 action)
│ pattern { action } │
│ ... │
├──────────┤
│ END { } │ (所有行之后跑一次)
└──────────┘
例子:
ls -l | awk 'BEGIN {sum=0} {sum+=$5} END {print "total:", sum}'
# 算所有文件总字节数
字段:$1 $2 ... $0
echo "a b c d" | awk '{print $1}' # a
echo "a b c d" | awk '{print $3}' # c
echo "a b c d" | awk '{print $0}' # a b c d 整行
echo "a b c d" | awk '{print NF}' # 4 字段数
echo "a b c d" | awk '{print $NF}' # d 最后一个字段
echo "a b c d" | awk '{print $(NF-1)}' # c 倒数第二
$NF 是日常救命:取最后一列,不需要数到底有多少列。
分隔符 -F
默认按任意连续空白分字段(空格、tab)。要改:
# CSV
awk -F, '{print $2}' data.csv
# 冒号分隔(/etc/passwd)
awk -F: '{print $1}' /etc/passwd # 列出所有用户名
# 多字符分隔
awk -F'::' '{print $2}' weird.log
# 正则分隔
awk -F'[ ,;]+' '{print $1, $3}' messy.log
或者在 BEGIN 里设:
awk 'BEGIN{FS=":"} {print $1}' /etc/passwd
-F 和 BEGIN{FS=":"} 等价。-F 更简短。
输出分隔符 OFS
echo "a b c" | awk 'BEGIN{OFS="|"} {print $1,$2,$3}'
# a|b|c
OFS(output field separator)控制 print 用逗号隔开的字段在输出时拿什么连接。默认是空格。
模式(匹配什么行)
awk '/pattern/' file # 等价 grep,打印匹配行
awk '!/pattern/' file # 等价 grep -v
awk '/start/,/end/' file # 范围(grep 干不了)
# 按字段判断
awk '$3 == "running"' file # 第三列等于 running
awk '$3 != "running"' file
awk '$5+0 > 80' # 第五列数字大于 80
awk 'NF > 0' file # 非空行
awk 'NR > 1' file # 跳过第一行(标题)
awk 'NR == FNR' file1 file2 # 处理 file1 时(NR == FNR)
NR = number of record(当前行号),FNR = current file's NR,NF = number of fields。
$5+0 强转数字:awk 字段默认是字符串,比较大小要 +0 转数字(或参与算术运算)。
最实用的 10 个例子
1. 取列
ps aux | awk '{print $2}' # PID
ps aux | awk '{print $11}' # 命令
kubectl get pods | awk 'NR>1 {print $1}' # pod 名(跳过表头)
df -h | awk 'NR>1 {print $1, $5}' # 文件系统 + 用量
2. 求和 / 计数
# 总字节数
ls -l | awk '{sum+=$5} END {print sum}'
# 计数
ps aux | awk '/nginx/ {count++} END {print count}'
# 行数(等价 wc -l)
awk 'END {print NR}' file
3. 平均
awk '{sum+=$3; count++} END {print sum/count}' file
4. 找最大 / 最小
awk '$3 > max {max=$3; line=$0} END {print line}' file
5. 去重统计(uniq -c 的强化版)
awk '{count[$1]++} END {for (k in count) print count[k], k}' file | sort -rn
count[$1]++ —— 关联数组(hash map)。awk 内置 hash 是它最强的功能之一。
6. 修改某列
# 给第二列加引号
echo "a 1 x" | awk '{print $1, "\""$2"\"", $3}'
# 给所有数字加 1
echo "1 2 3" | awk '{print $1+1, $2+1, $3+1}'
7. 多条件
awk '$3 == "Running" && $5 ~ /h$/' file
# 第 3 列是 Running 且第 5 列以 h 结尾
~ 是正则匹配,!~ 是不匹配。
8. 条件分支
df -h | awk 'NR>1 {if ($5+0 > 80) print "WARN:", $0; else print "OK:", $1}'
9. 跨行求和(按 key 分组)
# CSV:name, value
awk -F, '{sum[$1]+=$2} END {for (k in sum) print k, sum[k]}' data.csv
10. 第一列改大写
awk '{print toupper($1), $2, $3}' file
toupper() / tolower() / length() / substr(s, start, len) 是常用内置函数。
训练营典型用法
看哪些 pod 不是 Running
kubectl get pods -A | awk '$4 != "Running" && NR > 1'
统计每个节点的 pod 数
kubectl get pods -A -o wide | awk 'NR>1 {count[$8]++} END {for (n in count) print n, count[n]}'
看 /var/log/auth.log 里登录失败的 IP
grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn
df 报告超 80% 的分区
df -h | awk 'NR>1 && $5+0 > 80 {print $0}'
journalctl 找最常出现的错误模板
journalctl -u kubelet --since today -o cat \
| awk '{print $1, $2, $3, $4, $5}' \
| sort | uniq -c | sort -rn | head
awk 自带功能速查
内置变量
| 变量 | 含义 |
|---|---|
$0 | 整行 |
$1..$NF | 字段 |
NF | 当前行字段数 |
NR | 当前行号(全局) |
FNR | 当前文件内行号 |
FS | 输入分隔符(默认空白) |
OFS | 输出分隔符(默认空格) |
RS | 输入记录分隔符(默认换行) |
ORS | 输出记录分隔符 |
FILENAME | 当前文件名 |
内置函数
| 函数 | 用途 |
|---|---|
length(s) | 字符串长度 |
substr(s, start, len) | 子串 |
index(s, sub) | 找子串位置 |
split(s, arr, sep) | 按 sep 拆 s 到数组 arr |
toupper(s) / tolower(s) | 大小写 |
gsub(re, rep, target) | 全局替换 |
sub(re, rep, target) | 单次替换 |
match(s, re) | 正则匹配 |
sprintf(fmt, args...) | 格式化字符串(仿 C) |
printf("...", args) | 格式化输出 |
int(x) | 转整数 |
控制流
if (...) {...} else {...}
for (i=0; i<10; i++) {...}
for (k in arr) {...}
while (...) {...}
break / continue
awk 是个真·语言,但日常 1-2 行就够。写到三行以上就该用 Python 了。
常见踩坑
坑 1:字段索引错乱
ps aux 的输出格式:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1234 0.5 1.2 ... ... ? Ss May27 0:00 /usr/sbin/sshd -D
───────────
$11 起
$11 是命令的第一个 token;如果命令有空格,需要 $11, $12, ... 拼接。要拿整个命令:
ps aux | awk '{for(i=11;i<=NF;i++) printf "%s ", $i; print ""}'
或者更现代的写法用 pgrep -a、ps -eo cmd:
ps -eo pid,cmd
坑 2:字符串和数字混淆
echo "5" | awk '{if ($1 > "10") print "big"}' # awk 比较字符串:"5" > "10" 字典序 → big
echo "5" | awk '{if ($1+0 > 10) print "big"}' # 数字比较 → 不打印
字段都是字符串。要数字比较:永远 $N+0 或者参与算术运算。
坑 3:-F 是字符还是正则
awk -F. '{print $1}' file # 按 . 分?看 awk 实现
GNU awk(gawk)的 -F 默认是字符字面量。但单字符默认是字符,多字符是正则。最稳的写法:
awk -F'[.]' '{print $1}' file
坑 4:单引号里的引号
awk '{print "PID: " $2}' # 双引号在单引号外面 OK
awk '{print '"'"'PID: '"'"' $2}' # 这种嵌套很恶心
遇到要嵌引号,写脚本文件 -f script.awk,或者用 Python。
坑 5:用 awk 替代复杂逻辑
awk 能干很多事,但一旦超过 2-3 行就该停下问"这是 Python 干的活吗"。awk 没好的错误处理、没好的调试器、没结构化输出。