Linux awk 命令完全指南:从基础语法到运维实战
Linux awk 命令完全指南:从基础语法到运维实战
在 Linux 文本处理三剑客(grep
、sed
、awk
)中,awk 以其强大的字段级处理能力和灵活的格式化输出功能成为运维工程师的“瑞士军刀”。它不仅能按行解析文本,更能精准分割字段、实现复杂逻辑判断,尤其适合日志分析、数据统计、配置解析等场景。本文基于 CentOS 7(GNU awk 4.0.2) 环境,从基础语法到企业级实战,系统讲解 awk 的核心用法,所有命令均经过验证可直接复现。
一、awk 是什么?定位与核心能力
awk 诞生于 1977 年,得名于三位开发者(Aho、Weinberger、Kernighan)的首字母,本质是一种模式驱动的解释型编程语言,核心定位可概括为:
“按行读取文本 → 按字段分割数据 → 按模式匹配筛选 → 按需求格式化输出”
1.1 三剑客功能对比(精准定位)
工具 | 核心优势 | 适用场景 | 典型需求示例 |
---|---|---|---|
grep | 快速查找/过滤文本 | 按关键字匹配行、提取片段 | 筛选日志中的 ERROR 信息、过滤特定 IP |
sed | 批量编辑文本(替换/删除) | 按模式修改行内容、删除空行/注释 | 替换配置文件中的域名、清理日志注释 |
awk | 字段级处理、数据统计 | 解析结构化数据、格式化输出、统计 | 提取 /etc/passwd 用户信息、统计访问 Top10 IP |
1.2 学会 awk 能解决哪些问题?
- 结构化数据解析:处理 CSV、
/etc/passwd
、Nginx 日志等格式固定的数据; - 精准字段提取:按列号/分隔符提取目标数据(如从日志中提取 IP、URL、状态码);
- 条件逻辑判断:筛选符合条件的行(如 UID>1000 的普通用户、状态码为 5xx 的错误请求);
- 数据统计分析:统计单词频率、IP 访问次数、文件行数等;
- 格式化输出:自定义表头、字段分隔符,让输出更易读(如给数据加备注、对齐列)。
二、awk 基础:语法与核心概念
2.1 基本语法格式
awk 的语法由「模式(Pattern)」和「动作(Action)」组成,核心结构如下:
awk [选项] '模式{动作}' 数据来源
组成部分 | 说明 | 示例 |
---|---|---|
选项 | 常用 -F 分隔符 (指定字段分隔符)、-v 变量=值 (定义自定义变量) | awk -F: '{...}' /etc/passwd |
模式 | 筛选条件(如行号、正则、范围),不写模式则默认处理所有行 | NR==3 (第3行)、/root/ (含root的行) |
动作 | 处理逻辑(如 print 输出、变量计算),必须用 {} 包裹,多语句用 ; 分隔 | {print $1,$3} (输出第1、3列) |
数据来源 | 可直接跟文件名,或通过管道传递(` | )、标准输入( stdin`) |
2.2 核心概念:行(Record)与字段(Field)
awk 处理文本时,默认按“行-字段”二级结构解析数据,理解这两个概念是学好 awk 的关键:
概念 | 定义 | 控制变量 | 引用方式 |
---|---|---|---|
行(Record) | 每一行文本视为一条“记录”,行与行的分隔符默认是 \n (换行符) | RS (行分隔符) | $0 (当前行完整数据) |
字段(Field) | 每一行按“字段分隔符”分割为多个“字段”(列),默认分隔符是空格/制表符 | FS (字段分隔符) | $N (第N列,如 $1、$2) |
示例:解析一行文本
假设文本行:zack linux python music
,其“行-字段”结构如下:
概念 | 对应值 | 变量/语法 |
---|---|---|
行(Record) | zack linux python music | $0 |
字段数 | 4 | NF (内置变量) |
第1字段 | zack | $1 |
第4字段 | music | $4 |
最后1字段 | music | $NF |
倒数第2字段 | python | $(NF-1) |
三、awk 核心内置变量(必背)
awk 预定义了大量内置变量,用于控制解析规则和获取数据元信息,常用变量如下(按功能分类):
3.1 行与字段相关变量
变量 | 含义 | 示例(解析 zack linux python music ) |
---|---|---|
NR | 当前处理的全局行号(多文件累计) | 处理第1行时,NR=1 |
FNR | 当前文件的行号(多文件独立计数) | 处理第二个文件第3行时,FNR=3 |
NF | 当前行的字段总数(列数) | 该行有4列,NF=4 |
$N | 第N个字段(N为正整数) | $1=zack 、$4=music |
$0 | 当前行的完整数据 | $0=zack linux python music |
$NF | 当前行的最后一个字段 | $NF=music |
3.2 分隔符相关变量
变量 | 含义 | 默认值 | 示例(自定义) |
---|---|---|---|
FS | 输入字段分隔符(拆分列) | 空格/制表符 | FS=":" (按冒号拆分,解析 /etc/passwd ) |
OFS | 输出字段分隔符(拼接列) | 空格 | OFS="---" (输出用 --- 分隔列) |
RS | 输入行分隔符(拆分行) | \n (换行) | RS=" " (按空格拆分,单词当行) |
ORS | 输出行分隔符(拼接行) | \n (换行) | ORS="@@" (输出行用 @@ 结尾) |
3.3 其他常用变量
变量 | 含义 | 示例 |
---|---|---|
FILENAME | 当前处理的文件名 | 处理 /etc/passwd 时,FILENAME=/etc/passwd |
ARGC | 命令行参数的总数 | awk ... file1 file2 中,ARGC=3 |
ARGV | 命令行参数的数组(从0开始) | ARGV[0] = "awk" 、ARGV[1] = "file1" |
示例:内置变量实战
步骤1:创建测试文件 zack.log
# 生成10行数据,每行5个以空格分隔的字段(zack01~zack50)
echo zack{01..50} | xargs -n 5 > zack.log
步骤2:用内置变量解析数据
-
显示行号+字段数+完整行:
awk '{print "行号:"NR, "字段数:"NF, "内容:"$0}' zack.log
执行效果:
行号:1 字段数:5 内容:zack01 zack02 zack03 zack04 zack05 行号:2 字段数:5 内容:zack06 zack07 zack08 zack09 zack10 行号:3 字段数:5 内容:zack11 zack12 zack13 zack14 zack15
-
提取第2行的第1列和最后1列:
awk 'NR==2{print "第2行第1列:"$1, "第2行最后1列:"$NF}' zack.log
执行效果:
第2行第1列:zack06 第2行最后1列:zack10
四、awk 模式:如何精准匹配目标行?
awk 的“模式”是筛选行的核心,支持 4 种核心模式,以及特殊模式 BEGIN/END
,覆盖绝大多数场景。
4.1 行号模式:按行号筛选
通过 NR
(行号变量)指定具体行,支持“等于、范围、列表”三种用法:
模式语法 | 含义 | 示例命令(基于 zack.log ) |
---|---|---|
NR==N | 仅处理第N行 | awk 'NR==3{print $0}' zack.log (输出第3行) |
NR>=N && NR<=M | 处理第N到M行(含边界) | awk 'NR>=2&&NR<=4{print $1,$3}' zack.log (2-4行的1、3列) |
`NR~/(N | M | K)/` |
4.2 正则模式:按内容筛选
用 /正则表达式/
匹配行内容,支持基础正则(如 ^
行首、$
行尾、.
任意字符):
需求 | 正则模式 | 示例命令(解析 /etc/passwd ) |
---|---|---|
匹配 root 用户行 | /^root/ | awk -F: '/^root/{print $1,$NF}' /etc/passwd (输出root的Shell) |
匹配允许登录的用户 | /bash$/ | awk -F: '/bash$/{print $1}' /etc/passwd (Shell为bash的用户) |
匹配用户名含 zack 的行 | /zack/ | awk -F: '/zack/{print $1,$6}' /etc/passwd (输出zack的家目录) |
排除注释行(#开头) | !/^#/ | awk '!/^#/{print $0}' /etc/nginx/nginx.conf (过滤注释) |
4.3 比较模式:按字段/数值筛选
支持数值比较(==
、>
、<
、!=
、>=
、<=
)和字符串匹配(~
、!~
):
需求 | 比较模式 | 示例命令(解析 /etc/passwd ) |
---|---|---|
筛选 UID=0 的用户 | $3==0 | awk -F: '$3==0{print $1,"UID="$3}' /etc/passwd |
筛选 UID>1000 的普通用户 | $3>1000 | awk -F: '$3>1000{print $1,"普通用户"}' /etc/passwd |
筛选 Shell 不是 nologin 的 | $NF!~/nologin/ | awk -F: '$NF!~/nologin/{print $1,"可登录"}' /etc/passwd |
筛选家目录含 /home 的用户 | $6~/\/home/ (/需转义) | awk -F: '$6~/\/home/{print $1,$6}' /etc/passwd |
4.4 范围模式:按起始/结束条件筛选
用 模式1,模式2
匹配“从模式1到模式2”的所有行(包含边界行),适合批量筛选连续行:
# 示例:匹配从root行到daemon行的所有用户(含root和daemon)
awk -F: '/^root/,/^daemon/{print NR,$1}' /etc/passwd
执行效果:
1 root
2 bin
3 daemon
注意:范围模式是“非贪婪”的,找到第一个匹配模式2的行后,就停止当前范围的匹配。
4.5 特殊模式:BEGIN
与 END
BEGIN
:在 awk 读取文本之前执行,仅执行1次,常用于初始化变量、打印表头;END
:在 awk 读取所有文本之后执行,仅执行1次,常用于统计结果、打印总结。
示例:格式化输出 /etc/passwd
前5个用户
awk -F: '
# BEGIN:读取文件前执行(初始化变量、打印表头)
BEGIN{OFS="---" # 输出字段分隔符设为---print "行号---用户名---UID---家目录---登录Shell" # 表头
}
# 模式+动作:处理前5行
NR<=5{print NR,$1,$3,$6,$NF # 输出行号、用户名、UID、家目录、Shell
}
# END:读取文件后执行(总结)
END{print "共处理"NR"行数据,已输出前5行"
}' /etc/passwd
执行效果:
行号---用户名---UID---家目录---登录Shell
1---root---0---/root---/bin/bash
2---bin---1---/bin---/sbin/nologin
3---daemon---2---/sbin---/sbin/nologin
4---adm---3---/var/adm---/sbin/nologin
5---lp---4---/var/spool/lpd---/sbin/nologin
共处理45行数据,已输出前5行
五、awk 字段处理:自定义分隔符
默认情况下,awk 按“空格/制表符”分割字段,但实际场景中数据常以冒号(/etc/passwd
)、逗号(CSV)、@(邮箱)等分隔,需自定义 FS
(输入字段分隔符)。
5.1 指定分隔符的 3 种方式
方式 | 语法格式 | 示例(解析 zack:linux:python:music ) |
---|---|---|
选项 -F | awk -F 分隔符 '{...}' | awk -F: '{print $1,$3}' → zack python |
变量 -v FS=分隔符 | awk -v FS=分隔符 '{...}' | awk -v FS=: '{print $2,$4}' → linux music |
BEGIN 中定义 | awk 'BEGIN{FS=分隔符}{...}' | awk 'BEGIN{FS=:}{print $1,$4}' → zack music |
推荐用法:简单分隔符用
-F
(如-F:
),复杂分隔符(含正则)在BEGIN
中定义。
5.2 高级分隔符:多字符/正则分隔
awk 支持用正则表达式作为分隔符,解决“多字符分隔”“不确定分隔符”等复杂场景:
需求 | 分隔符设置 | 示例命令(解析 zack@linux#python-music ) |
---|---|---|
按 @/😕#/- 任意一个分隔 | FS="[#@:-]" | awk -v FS="[#@:-]" '{print $1,$2,$3,$4}' → zack linux python music |
按多个连续空格分隔 | FS="[ ]+" | awk -v FS="[ ]+" '{print $1,$2}' (处理 zack linux ) → zack linux |
按 [ 或 ] 分隔 | `FS="\[ | \]"`(需转义) |
示例:解析 CSV 文件
假设有 user.csv
文件(内容:用户名,密码,身份证号
):
zack1,123456,110101199001011234
zack2,654321,310101199505056789
zack3,abcdef,440101200010101357
需求:提取“用户名+身份证号”,用 ---
分隔输出,并添加表头:
awk -v FS=',' -v OFS='---' '
BEGIN{print "用户名---身份证号"} # 表头
{print $1,$3} # 输出第1列(用户名)和第3列(身份证号)
' user.csv
执行效果:
用户名---身份证号
zack1---110101199001011234
zack2---310101199505056789
zack3---440101200010101357
六、awk 运维实战场景(企业级)
6.1 场景1:统计 Nginx 访问日志中访问量 Top5 的 IP
日志格式(/var/log/nginx/access.log
):
192.168.1.100 - - [15/Oct/2024:10:00:00 +0800] "GET /index.html HTTP/1.1" 200 1234
192.168.1.101 - - [15/Oct/2024:10:01:00 +0800] "GET /login.html HTTP/1.1" 200 5678
192.168.1.100 - - [15/Oct/2024:10:02:00 +0800] "POST /submit HTTP/1.1" 200 9012
命令:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -5
解析:
awk '{print $1}'
:提取日志第1列(客户端 IP);sort
:按 IP 排序(为uniq
去重做准备);uniq -c
:统计每个 IP 的重复次数(-c
表示“计数”);sort -nr
:按计数倒序(-n
数值排序,-r
倒序);head -5
:取前 5 个 IP。
执行效果:
120 192.168.1.10089 192.168.1.10156 192.168.1.10232 192.168.1.10328 192.168.1.104
6.2 场景2:提取网卡 IP 地址(排除 127.0.0.1)
命令:
ip addr show | awk '/inet / && !/127.0.0.1/{gsub(/\/.*/,"",$2);print "网卡"$NF"的IP:"$2}'
解析:
ip addr show
:获取所有网卡的网络信息;/inet / && !/127.0.0.1/
:匹配含inet
(IP 行)且不含127.0.0.1
(本地回环)的行;gsub(/\/.*/,"",$2)
:删除$2
(IP 字段)中的/
及后面内容(如192.168.1.100/24
→192.168.1.100
);print "网卡"$NF"的IP:"$2
:输出网卡名($NF
,如ens33
)和 IP($2
)。
执行效果:
网卡ens33的IP:192.168.1.100
网卡docker0的IP:172.17.0.1
6.3 场景3:判断 22(SSH)和 80(HTTP)端口是否存活
命令:
netstat -tuln | awk '
BEGIN{print "端口状态检测结果:\n端口\t状态"} # 表头
$4~/:22$/{port22="已开启"} # 匹配22端口
$4~/:80$/{port80="已开启"} # 匹配80端口
END{# 三元运算符:变量存在则输出“已开启”,否则“未开启”print "22\t"(port22?port22:"未开启")print "80\t"(port80?port80:"未开启")
}'
执行效果:
端口状态检测结果:
端口 状态
22 已开启
80 未开启
6.4 场景4:统计文本中单词出现频率 Top5
假设有 english.log
文件(内容:I like zack, zack likes linux, linux is good
):
命令:
awk -v RS='[^a-zA-Z]+' '{if($0!="")count[$0]++}END{for(word in count)print count[word],word}' english.log | sort -nr | head -5
解析:
RS='[^a-zA-Z]+'
:按“非字母”分割行(每个单词视为一条独立行);if($0!="")count[$0]++
:非空单词计入count
数组(key=单词
,value=出现次数
);for(word in count)print count[word],word
:遍历数组,输出次数和单词;sort -nr | head -5
:按次数倒序,取 Top5。
执行效果:
2 zack
2 linux
1 I
1 like
1 likes
七、awk 常见问题与避坑指南
7.1 问题1:字段分隔符设置不生效?
原因1:分隔符含特殊字符未转义
awk 中 |
、[
、\
等是特殊字符,需用 \
转义,或放在 []
中:
- 错误:
awk -F| '{...}'
(|
被解析为“或”); - 正确:
awk -F'|' '{...}'
或awk -F\\| '{...}'
。
原因2:同时用 -F
和 BEGIN{FS=}
定义分隔符
-F
优先级高于 BEGIN{FS=}
,会覆盖后者,建议统一用一种方式:
- 推荐:简单分隔符用
-F
,复杂分隔符用BEGIN{FS=}
。
7.2 问题2:print
输出时字段间分隔符不对?
解决方案:设置 OFS
(输出字段分隔符)
默认 OFS
是空格,若需自定义(如 ---
、,
),需显式设置:
# 示例:输出用 --- 分隔字段
awk -F: -v OFS='---' '{print $1,$3,$NF}' /etc/passwd
7.3 问题3:处理多文件时行号混乱?
原因:NR
是全局行号,多文件会累计
解决方案:用 FNR
代替 NR
(单个文件独立行号)
# 示例:分别显示 file1 和 file2 的前2行(每行标注文件名和行号)
awk 'FNR<=2{print FILENAME"第"FNR"行:"$0}' file1 file2
7.4 问题4:处理中文字符时乱码或匹配失败?
解决方案:设置字符集为 LC_ALL=C
中文字符会影响字段分割和正则匹配,执行前先设置环境变量:
export LC_ALL=C # 临时生效,永久生效需添加到 ~/.bashrc
八、awk 练习题(含答案)
8.1 基础题:字段提取与格式化
-
需求:解析
echo "zack linux python music"
,输出“姓名:zack,爱好:linux python music”;
答案:echo "zack linux python music" | awk '{print "姓名:"$1",爱好:"$2,$3,$4}'
-
需求:解析
/etc/passwd
,输出前3个用户的“用户名-UID-家目录”(用-
分隔);
答案:awk -F: -v OFS='-' 'NR<=3{print $1,$3,$6}' /etc/passwd
8.2 进阶题:复杂分隔与条件筛选
-
需求:解析
echo "zack:://---linux:..python"
,提取为zack linux python
;
答案:echo "zack:://---linux:..python" | awk -v FS='[^a-zA-Z]+' '{print $1,$2,$3}'
-
需求:筛选
/etc/passwd
中“UID>500 且 Shell 为/bin/bash
”的用户,输出用户名和家目录;
答案:awk -F: '$3>500 && $NF=="/bin/bash"{print "用户名:"$1",家目录:"$6}' /etc/passwd
九、总结与学习资源
9.1 核心知识点总结
awk 的核心是“行处理 + 字段分割 + 模式匹配 + 格式化输出”,需重点掌握:
- 内置变量:
NR
(行号)、NF
(字段数)、$N
(字段引用)、FS/OFS
(分隔符); - 模式匹配:行号模式(
NR==N
)、正则模式(/xxx/
)、比较模式($3>1000
); - 实战场景:日志统计、IP 提取、CSV 解析、端口检测。
9.2 推荐学习资源
- 官方文档:GNU awk 手册(https://www.gnu.org/software/gawk/manual/);
- 在线工具:Regex101(https://regex101.com/,测试正则表达式)、AwkFiddle(在线调试 awk 代码);
- 书籍:《sed与awk(第2版)》、《Linux命令行与Shell脚本编程大全》。
通过本文的学习,可解决 80% 以上的 Linux 文本处理需求。awk 的深度在于其编程语言特性(数组、循环、函数),建议在实战中逐步探索,例如用 awk
编写脚本处理复杂日志分析任务,进一步提升运维效率。