🔍搜索⌘K

Linux 下 AWK 的基本使用与实战指南

概述

AWK 这名字来自三位作者的姓首字母:Aho、Weinberger、Kernighan。官方说法是它一门“模式扫描与文本处理语言”,说人话就是:它不单是 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 自定义变量

自定义变量就是你自己定义来存临时数据的(比如计数、中间结果),有两种定义方式:

  1. 用 -v 选项定义:在 AWK 脚本跑之前就把变量定好,作用域覆盖整个脚本(包括 BEGIN 块)。
# 定义变量 age=30,在 BEGIN 块中输出
awk -v age=30 'BEGIN{print "Age:", age}'
  1. 在脚本内部定义:在 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 是 $0awk ‘{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 文本处理里的一把多面手,它好就好在:

  1. 简洁高效:靠“模式 + 动作”这一套,一行命令就能把复杂的文本处理干完;
  2. 功能全:变量、数组、函数、流程控制都支持,复杂脚本也写得出来;
  3. 通用:日志分析、数据统计、文本格式化这些场景都能上。

学 AWK 最关键的是把“逐行处理”这个核心逻辑搞懂,再把内置变量、模式匹配、控制语句用熟。多找实战练练(比如分析日志、统计数据),上手很快,Linux 下干活的效率也能明显提上来。