Linux 下 AWK 的基本使用与实战指南
概述
AWK 的全称是 Aho-Weinberg-Kernighan,得名于其三位创始人 ——Alfred Aho、Peter Weinberger 和 Brian Kernighan 姓氏的首字母。在官方定义中,AWK 是一种 模式扫描与文本处理语言(pattern scanning and text processing language),它不仅是 Linux 系统中强大的文本分析工具,更是一门具备完整语法的脚本语言,能够通过「模式匹配 + 动作执行」的逻辑,高效处理结构化或非结构化文本。
AWK 的核心语法框架
AWK 的工作逻辑围绕「模式(PATTERN) + 动作(ACTION)」展开,基本语法格式可拆解为:
PATTERN { ACTION STATEMENTS }
- 模式(PATTERN):定义「何时执行动作」,例如匹配特定行、特定字段或行范围。若省略模式,则默认对每一行执行动作。
- 动作(ACTION):定义「执行什么操作」,由一系列语句组成,语句间需用 分号(;) 分隔。核心动作语句包括 print(简单输出)和 printf(格式化输出)。
- 执行流程:AWK 会逐行读取输入文本(或文件),对每一行先判断是否匹配「模式」,若匹配则执行对应的「动作」,直至所有行处理完成。
AWK 的常用选项
在命令行调用 AWK 时,可通过选项调整其行为,常用选项如下:
选项 | 功能说明 | 示例 |
---|---|---|
-F fs | 指定输入字段分隔符(field separator),默认分隔符为「空白字符」(空格 / 制表符) | awk -F ':' '{print $1}' /etc/passwd(以冒号分隔字段) |
-v var=value | 定义自定义变量并赋值,变量作用域覆盖整个 AWK 脚本(包括 BEGIN 块) | awk -v name="Alice" 'BEGIN{print name}' |
-f script.awk | 从指定文件(如 script.awk)中读取 AWK 脚本,而非在命令行直接写脚本 | awk -f analyze.awk data.txt |
AWK 的核心语法详解
1. 输出语句:print 与 printf
输出是 AWK 最基础的功能,print 适合简单输出,printf 适合格式化输出,二者的差异与用法如下:
1.1 print 语句
print 是 AWK 的默认输出工具,语法简洁,无需指定格式,但灵活性较低。
语法:print item1, item2, ...
核心特点:
- 多个输出项用 逗号(,) 分隔,输出时会自动用「输出字段分隔符(OFS)」连接(默认是空格);
- 输出项可以是字符串(需用双引号包裹)、字段(如 $1)、变量或表达式;
- 若省略输出项(即 print),默认输出当前行的完整内容(等价于 print $0);
- 执行后会自动换行。
示例:
# 输出 /etc/passwd 中每行的第1个字段(用户名)和第7个字段(登录Shell)
awk -F ':' '{print $1, "->", $7}' /etc/passwd
# 省略输出项,输出当前行完整内容
awk '/root/{print}' /etc/passwd # 等价于 awk '/root/{print $0}' /etc/passwd
1.2 printf 语句
printf 支持自定义输出格式,适合需要统一排版的场景(如对齐、保留小数等),但需显式控制换行。
语法:printf "FORMAT", item1, item2, ...
核心特点:
- FORMAT(格式串)必须指定,用于定义后续输出项的格式;
- 不会自动换行,需在格式串中显式添加换行符 \n;
- 格式串中需为每个输出项匹配一个「格式符」(如 %s 对应字符串,%d 对应整数)。
常用格式符:
格式符 功能说明 示例 %s 输出字符串 printf "%s\n", "username" %d/%i 输出十进制整数 printf "UID: %d\n", $3(输出用户 ID) %f 输出浮点数 printf "Percent: %.2f%%\n", 75.3(保留 2 位小数) %e/%E 科学计数法输出 printf "%e\n", 12345(输出 1.234500e+04) %% 输出百分号本身(转义) printf "50%%\n"(输出 50%) 格式修饰符(可选,用于调整格式):
- 宽度:控制输出项的显示宽度,不足时默认右对齐,例如 %15s 表示字符串占 15 个字符宽度;
- -:左对齐(需配合宽度使用),例如 %-15s;
- .#:控制小数精度(仅用于浮点数),例如 %5.2f 表示占 5 个字符宽度,保留 2 位小数。
示例:
# 格式化输出 /etc/passwd,用户名左对齐占15位,UID右对齐占5位
awk -F ':' '{printf "User: %-15s UID: %5d\n", $1, $3}' /etc/passwd
# 输出磁盘使用率,保留1位小数并添加百分号
df -h | awk '/\/$/{printf "Root Usage: %.1f%%\n", $5+0}' # $5+0 转为数值(去除%)
2. AWK 的变量体系
AWK 的变量分为「内置变量」(系统预定义,直接使用)和「自定义变量」(用户自行定义),二者共同支撑脚本的逻辑处理。
2.1 内置变量(核心常用)
内置变量是 AWK 对文本处理的核心支撑,无需定义即可直接使用,以下是高频内置变量:
变量名 | 中文含义 | 功能说明 | 示例 |
---|---|---|---|
$0 | 当前行 | 表示当前正在处理的完整行内容 | awk '{print $0}' file.txt(输出所有行) |
$n | 第 n 个字段 | 表示当前行的第 n 个字段(n 为正整数),字段由 FS 分隔 | awk -F ':' '{print $3}' /etc/passwd(输出 UID) |
FS | 输入字段分隔符 | 定义「输入时如何分割字段」,默认是空白字符,可通过 -v FS=xxx 或 BEGIN{FS=xxx} 修改 | awk -v FS=';' '{print $1}' data.csv(处理 CSV 文件) |
OFS | 输出字段分隔符 | 定义「输出时字段间的连接符」,默认是空格 | awk -v FS=':' -v OFS='-' '{print $1, $3}' /etc/passwd(用 - 连接字段) |
NR | 总记录数 | 表示「已处理的总行数」,多文件处理时会累计计数 | awk '{print NR, $0}' file1.txt file2.txt(显示行号) |
FNR | 单个文件记录数 | 表示「当前文件已处理的行数」,多文件处理时每个文件重新计数 | awk '{print FNR, $0}' file1.txt file2.txt(每个文件单独行号) |
NF | 字段总数 | 表示当前行的「字段总数」,$NF 可直接获取「最后一个字段」 | awk -F ':' '{print $1, "->", $NF}' /etc/passwd(输出最后一个字段) |
FILENAME | 当前文件名 | 表示「当前正在处理的文件名」 | awk '{print FILENAME, $0}' file1.txt file2.txt(显示文件名 + 行内容) |
RS | 输入记录分隔符 | 定义「如何分割行」,默认是换行符(\n),可用于处理特殊换行格式(如 Windows 的\r\n) | awk -v RS='\r\n' '{print $0}' winfile.txt(处理 Windows 换行) |
ORS | 输出记录分隔符 | 定义「输出时行之间的分隔符」,默认是换行符 | awk -v ORS='---\n' '{print $1}' /etc/passwd(用 --- 分隔输出行) |
- 示例:
# 显示 /etc/passwd 的行号、文件名和每行的最后一个字段
awk -F ':' '{print NR, FILENAME, $NF}' /etc/passwd
# 处理多个文件,显示每个文件的单独行号
awk '{print FNR, $0}' /etc/passwd /etc/hosts
2.2 自定义变量
自定义变量由用户定义,用于存储临时数据(如计数、中间结果等),有两种定义方式:
- 通过 -v 选项定义:在 AWK 脚本执行前定义变量,作用域覆盖整个脚本(包括 BEGIN 块)。
# 定义变量 age=30,在 BEGIN 块中输出
awk -v age=30 'BEGIN{print "Age:", age}'
- 在脚本内部定义:在 BEGIN 块或动作块中直接赋值,变量作用域从定义处开始。
# 在 BEGIN 块中定义变量 name,在动作块中使用
awk 'BEGIN{name="Bob"} {print name, "->", $1}' /etc/passwd
- 注意:AWK 变量区分大小写(如 name 和 Name 是两个不同变量),且无需声明类型(自动根据值判断是字符串还是数值)。
3. 操作符:支撑逻辑与计算
AWK 支持丰富的操作符,涵盖算术、比较、逻辑等场景,以下是核心常用操作符:
3.1 算术操作符
用于数值计算,支持常见的加减乘除等运算:
- 基础运算:+(加)、-(减)、*(乘)、/(除)、%(取余)、^(幂运算);
- 自增自减:++(自增 1)、--(自减 1);
- 正负号:+x(转为数值)、-x(取负)。
- 示例:
# 计算 /etc/passwd 中 UID 大于 1000 的用户数量
awk -F ':' '$3>1000{count++} END{print "Common Users:", count}' /etc/passwd
# 计算 1-100 的总和(BEGIN 块中执行,无需输入文件)
awk 'BEGIN{sum=0; for(i=1;i<=100;i++) sum+=i; print "Sum:", sum}'
3.2 比较操作符
用于判断条件是否成立(结果为「真」或「假」),常用场景包括字段比较、数值比较:
- 基础比较:>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、==(等于)、!=(不等于);
- 模式匹配:(匹配正则)、!(不匹配正则)。
- 示例:
# 输出 UID 等于 0 的用户(root 用户)
awk -F ':' '$3==0{print $1}' /etc/passwd
# 输出登录Shell为 /bin/bash 的用户(正则匹配)
awk -F ':' '$NF~=/\/bin\/bash$/{print $1}' /etc/passwd
# 输出注释行(以 # 开头)或空行以外的内容(正则不匹配)
awk '!/^#|^$/{print $0}' /etc/fstab
3.3 逻辑操作符
用于组合多个条件,实现复杂判断:
- &&:逻辑与(两个条件同时成立);
- ||:逻辑或(两个条件至少一个成立);
- !:逻辑非(条件不成立)。
- 示例:
# 输出 UID 大于 1000 且 Shell 为 /bin/bash 的普通用户
awk -F ':' '$3>1000 && $NF=="/bin/bash"{print $1}' /etc/passwd
# 输出 UID 小于 100 或 UID 大于 60000 的用户(系统用户或特殊用户)
awk -F ':' '$3<100 || $3>60000{print $1, $3}' /etc/passwd
3.4 赋值操作符
用于变量赋值,支持简化写法:
- 基础赋值:=(直接赋值);
- 复合赋值:+=(加后赋值)、-=(减后赋值)、*=(乘后赋值)、/=(除后赋值)。
- 示例:
# 统计每个文件系统类型的出现次数(复合赋值)
awk '/^UUID/{fs[$3]++} END{for(i in fs) print i, fs[i]}' /etc/fstab
4. 模式(PATTERN):控制动作执行时机
模式是 AWK 的「筛选器」,定义「哪些行需要执行动作」,常用模式包括以下 5 类:
4.1 空模式(默认)
若省略模式,动作会对每一行执行,是最常用的模式之一。
# 输出所有行的第1个字段(空模式,对每一行执行动作)
awk -F ':' '{print $1}' /etc/passwd
4.2 正则模式(/regexp/)
仅对「匹配正则表达式」的行执行动作,格式为 /正则表达式/。
# 输出包含 "root" 的行
awk '/root/{print $0}' /etc/passwd
# 输出以 "bash" 结尾的行($NF 是最后一个字段)
awk -F ':' '$NF~/bash$/{print $1}' /etc/passwd
4.3 关系表达式模式
仅对「关系表达式为真」的行执行动作,常见场景是字段或数值比较。
# 输出 UID 大于等于 1000 的普通用户
awk -F ':' '$3>=1000{print $1, $3}' /etc/passwd
# 输出字段数大于 5 的行(NF 是字段总数)
awk 'NF>5{print $0}' /etc/fstab
4.4 行范围模式(/pat1/,/pat2/)
对「从匹配 pat1 的行开始,到匹配 pat2 的行结束」的范围内所有行执行动作,注意范围是闭区间,且 pat1 和 pat2 可以是正则或关系表达式。
# 输出从第2行到第10行的内容(用 NR 表示行号)
awk 'NR==2,NR==10{print NR, $0}' /etc/passwd
# 输出从 "root" 行到 "bin" 行之间的内容
awk '/root/,/bin/{print $0}' /etc/passwd
4.5 BEGIN/END 模式(特殊模式)
BEGIN 和 END 是 AWK 的特殊模式,不依赖输入文本,仅执行一次:
- BEGIN{动作}:在读取输入文本前执行一次,常用于初始化变量、打印表头;
- END{动作}:在所有输入文本处理完成后执行一次,常用于输出最终结果(如统计汇总)。
- 示例:
# BEGIN 打印表头,END 打印汇总,中间处理数据
awk -F ':' '
BEGIN{print "=== User List ==="; print "Username | UID"} # 表头
$3>=1000{print $1, "|", $3; count++} # 处理普通用户
END{print "=== Total Common Users:", count, "==="} # 汇总
' /etc/passwd
5. 控制语句:实现复杂逻辑
AWK 支持常见的流程控制语句,如 if-else、for、while 等,用于实现循环、分支等复杂逻辑。
5.1 if-else 语句(分支判断)
用于根据条件执行不同动作,语法与 Shell 或 C 语言类似:
- 基础语法:if(条件) {动作 1} else {动作 2}
- 嵌套语法:if(条件 1) {动作 1} else if(条件 2) {动作 2} else {动作 3}
- 示例:
# 根据 UID 分类输出用户类型
awk -F ':' '
{
if($3==0) {print $1, "-> Admin"}
else if($3>=1000) {print $1, "-> Common User"}
else {print $1, "-> System User"}
}
' /etc/passwd
5.2 for 循环(固定次数循环)
用于已知循环次数的场景,语法为 for(初始化; 条件; 增量) {动作},也支持遍历数组(for(var in array))。
- 示例:
# 遍历当前行的所有字段,输出字段内容和长度
awk '/^linux16/{
for(i=1; i<=NF; i++) { # i 从1到NF(字段总数)
print "Field", i, ":", $i, "| Length:", length($i)
}
}' /etc/grub2.cfg
# 遍历数组(统计每个IP的访问次数)
awk '{ip[$1]++} # 记录每个IP的出现次数
END{
for(i in ip) { # 遍历数组 ip 的所有索引(IP地址)
print "IP:", i, "-> Count:", ip[i]
}
}
' /var/log/httpd/access_log
5.3 while 循环(条件循环)
用于未知循环次数、仅知道终止条件的场景,语法为 while(条件) {动作}。
- 示例:
# 遍历当前行的字段,仅输出长度大于7的字段
awk '/^linux16/{
i=1
while(i<=NF) {
if(length($i)>7) {print $i}
i++
}
}' /etc/grub2.cfg
5.4 next 语句(跳过当前行)
next 语句会立即跳过当前行的后续处理,直接进入下一行的处理,常用于过滤不需要的行。
# 仅输出 UID 为偶数的用户(跳过奇数UID的行)
awk -F ':' '{
if($3%2!=0) next # UID为奇数则跳过
print $1, "-> UID:", $3
}' /etc/passwd
6. 数组:处理键值对数据
AWK 仅支持「关联数组」(Associative Array),即数组索引可以是任意字符串(而非仅整数),非常适合存储键值对数据(如统计结果、映射关系等)。
6.1 数组的定义与使用
- 定义:无需声明,直接通过 数组名[索引] = 值 赋值即可,若索引不存在则自动创建。
- 访问:通过 数组名[索引] 访问值,若索引不存在则返回空串。
- 判断索引存在:通过 索引 in 数组名 判断,返回「真」或「假」。
- 遍历数组:通过 for(变量 in 数组名) 遍历,变量会依次取数组的所有索引。
- 示例:
# 1. 定义数组(星期映射)并遍历
awk 'BEGIN{
week["mon"]="Monday"
week["tue"]="Tuesday"
# 判断索引是否存在
if("wed" in week) {print week["wed"]}
else {print "wed not found"}
# 遍历数组
for(day in week) {
print day, "->", week[day]
}
}'
# 2. 统计 TCP 连接状态(ESTABLISHED/LISTEN 等)
netstat -tan | awk '
/^tcp/ {state[$NF]++} # $NF 是连接状态,作为数组索引
END{
print "TCP Connection Status:"
for(s in state) {
print s, ":", state[s]
}
}
'
6.2 数组的删除
通过 delete 数组名[索引] 删除指定索引的元素,或通过 delete 数组名 删除整个数组。
awk 'BEGIN{
arr["a"]=1; arr["b"]=2
delete arr["a"] # 删除索引 a
for(k in arr) print k, arr[k] # 仅输出 b 2
delete arr # 删除整个数组
}'
7. 函数:简化重复操作
AWK 提供丰富的「内置函数」,同时支持「自定义函数」,用于封装重复逻辑。
7.1 常用内置函数
内置函数无需定义,直接调用即可,以下是高频函数:
函数 | 功能说明 | 示例 |
---|---|---|
length(s) | 返回字符串 s 的长度,若省略 s 则返回当前行长度 | awk '{print length($1)}' /etc/passwd(输出用户名长度) |
sub(r, s, t) | 在字符串 t 中,将第一个匹配正则 r 的内容替换为 s,默认 t 是 $0 | awk '{sub(/root/, "ROOT"); print $0}' /etc/passwd(替换第一个 root) |
gsub(r, s, t) | 在字符串 t 中,将所有匹配正则 r 的内容替换为 s(全局替换) | awk '{gsub(/root/, "ROOT"); print $0}' /etc/passwd(替换所有 root) |
split(s, arr, r) | 以正则 r 为分隔符,将字符串 s 分割为数组 arr,索引从 1 开始 | awk '{split($0, arr, ":"); print arr[1]}' /etc/passwd(分割行为数组) |
rand() | 返回 0~1 之间的随机数(需配合 srand() 初始化随机种子) | awk 'BEGIN{srand(); print rand()}'(输出随机数) |
- 示例:
# 分割 IP 地址(提取 netstat 中的客户端IP,去除端口)
netstat -tan | awk '/^tcp/ {
split($5, ip, ":") # $5 是 "IP:端口",以:分割
count[ip[1]]++ # ip[1] 是纯IP地址
} END{
for(i in count) print "IP:", i, "-> Count:", count[i]
}'
7.2 自定义函数
若内置函数无法满足需求,可自定义函数,语法如下:
function 函数名(参数1, 参数2, ...) {
函数体(动作语句)
return 返回值 # 可选
}
- 示例:
# 自定义函数:计算两个数的和
awk '
function add(a, b) {
return a + b
}
BEGIN{
sum = add(10, 20)
print "Sum:", sum # 输出 Sum: 30
}
'
AWK 实战示例
以下是 AWK 在实际工作中的高频使用场景,覆盖文本筛选、统计、格式化等需求:
1. 筛选特定行与字段
# 1. 输出 /etc/passwd 中第 1-5 行的用户名
awk -F ':' 'NR>=1 && NR<=5{print $1}' /etc/passwd
# 2. 输出 /etc/fstab 中非注释、非空行的第1和第3个字段(设备与文件系统类型)
awk '!/^#|^$/{print $1, $3}' /etc/fstab
2. 文本统计与汇总
# 1. 统计 /var/log/httpd/access_log 中每个IP的访问次数(降序排列)
awk '{ip[$1]++} END{for(i in ip) print ip[i], i}' /var/log/httpd/access_log | sort -nr
# 2. 统计 /etc/passwd 中不同登录Shell的用户数量
awk -F ':' '{shell[$NF]++} END{for(s in shell) print s, ":", shell[s]}' /etc/passwd
# 3. 计算文件的总行数、总字段数、平均每行字段数
awk '{
total_lines++
total_fields += NF
} END{
print "Total Lines:", total_lines
print "Total Fields:", total_fields
print "Avg Fields per Line:", total_fields/total_lines
}' /etc/passwd
3. 格式化输出
# 1. 格式化输出用户信息(用户名左对齐15位,UID右对齐5位,Shell左对齐20位)
awk -F ':' '{
printf "User: %-15s UID: %5d Shell: %-20s\n", $1, $3, $NF
}' /etc/passwd
# 2. 输出磁盘使用率(仅保留1位小数,添加百分号,使用率超过80%标红)
df -h | awk '/^\/dev/{
usage = $5 + 0 # 转为数值(去除%)
if(usage>80) {
printf "\033[31m%s: %.1f%%\033[0m\n", $1, usage # 红色
} else {
printf "%s: %.1f%%\n", $1, usage
}
}'
4. 结合 Shell 变量
通过 ' " $VAR " ' 的格式,在 AWK 中引用 Shell 变量(需注意引号嵌套):
# 定义 Shell 变量 uid=1000,在 AWK 中引用并筛选用户
uid=1000
awk -F ':' -v awk_uid="$uid" '$3==awk_uid{print $1}' /etc/passwd
5. 从脚本文件执行 AWK
将复杂的 AWK 逻辑写入脚本文件(如 user_analyze.awk),便于复用:
# user_analyze.awk 脚本内容
BEGIN {
FS = ":" # 设置输入字段分隔符
print "=== User Analysis Report ==="
print "Type | Username | UID"
print "-----------------------------"
}
# 分类统计用户
$3 == 0 {
print "Admin |", $1, "|", $3
admin_count++
}
$3 >= 1000 {
print "Common User|", $1, "|", $3
common_count++
}
$3 > 0 && $3 < 1000 {
print "System User|", $1, "|", $3
system_count++
}
END {
print "-----------------------------"
print "Total Admin:", admin_count
print "Total Common User:", common_count
print "Total System User:", system_count
}
执行脚本:
awk -f user_analyze.awk /etc/passwd
总结
AWK 是 Linux 文本处理领域的「瑞士军刀」,其核心优势在于:
- 简洁高效:通过「模式 + 动作」的逻辑,一行命令即可完成复杂文本处理;
- 功能全面:支持变量、数组、函数、流程控制,可编写复杂脚本;
- 通用性强:适用于日志分析、数据统计、文本格式化等各类场景。
掌握 AWK 的关键在于理解「逐行处理」的核心逻辑,并熟练运用内置变量、模式匹配和控制语句。通过实战练习(如分析日志、统计数据),可快速提升 AWK 应用能力,显著提高 Linux 环境下的工作效率。