AWK命令使用

AWK 是一种文本处理语言,以处理数据、生成报告见长,兼具编程语言的灵活性和命令行工具的便捷性。它的核心优势是:按行处理文本、自动分割字段、支持条件判断和循环,适用于日志分析、数据提取、格式转换等场景。

一、AWK 基础入门

1.1 什么是 AWK?

  • 命名来源:取自三位创始人姓氏首字母(Aho、Weinberger、Kernighan)。
  • 定位:文本处理工具 + 解释型编程语言
  • 核心思想:逐行读取文本,按指定规则分割字段,执行自定义操作
  • 特点:
    • 无需编译,直接运行;
    • 自动处理字段分割(默认空格/制表符);
    • 内置变量、函数丰富,支持正则表达式;
    • 可处理大文件(逐行读取,内存占用低)。

1.2 安装与运行方式

AWK 是类 Unix 系统(Linux、macOS)内置工具,Windows 需安装 Git Bash、WSL 或 Cygwin。

运行方式(3种核心形式)
(1)命令行直接执行(单行命令)

格式:

awk '条件{操作}' 文件名

示例:读取 test.txt,打印所有行

# 准备测试文件
cat > test.txt << EOF
Alice 25 Engineer
Bob 30 Designer
Charlie 28 ProductManager
EOF

# 执行AWK命令
awk '{print}' test.txt

执行结果

Alice 25 Engineer
Bob 30 Designer
Charlie 28 ProductManager

简化写法(1 表示条件为真,默认执行 print):

awk 1 test.txt

执行结果:与上面完全一致。

(2)多行脚本(适合复杂逻辑)

格式:

awk '
条件1 {操作1}
条件2 {操作2}
' 文件名

示例:打印行号和内容

awk '
{print NR ": " $0}  # NR 是内置变量,代表行号;$0 代表整行内容
' test.txt

执行结果

1: Alice 25 Engineer
2: Bob 30 Designer
3: Charlie 28 ProductManager
(3)脚本文件(复用性强)

将 AWK 代码写入 .awk 文件,通过 -f 参数执行:

# 1. 创建脚本文件 script.awk
cat > script.awk << EOF
{print "行" NR ": " $0}
EOF

# 2. 执行脚本
awk -f script.awk test.txt

执行结果

行1: Alice 25 Engineer
行2: Bob 30 Designer
行3: Charlie 28 ProductManager

1.3 核心概念:字段、记录与内置变量

AWK 处理文本时,会自动将数据拆分为「记录」和「字段」:

  • 记录(Record):默认以「换行符」分隔,即一行是一条记录(对应变量 $0)。
  • 字段(Field):默认以「空格/制表符」分隔,字段编号从 $1 开始($1=第1字段,$2=第2字段,…,$NF=最后一个字段)。
常用内置变量(必背)
变量名 含义 示例
$0 整行文本(当前记录) print $0 打印整行
$n 第 n 个字段(n≥1) $3 表示第3个字段
NF 当前行的字段总数 print NF 打印每行字段数
NR 已读取的总记录数(行号) print NR 打印当前行号
FNR 每个文件的独立行号(多文件处理) 处理2个文件时,各文件行号重新计数
FS 字段分隔符(默认:空格/制表符) FS="," 以逗号分隔字段
OFS 输出字段分隔符(默认:空格) `OFS="
RS 记录分隔符(默认:换行符 \n RS=";" 以分号分隔记录
ORS 输出记录分隔符(默认:换行符) ORS="\n---\n" 每行输出后加分隔线
FILENAME 当前处理的文件名 多文件处理时标识文件

示例:使用内置变量处理文本

awk '{print "行" NR ", 姓名:" $1 ", 年龄:" $2 ", 职业:" $3 ", 字段数:" NF}' test.txt

执行结果

行1, 姓名:Alice, 年龄:25, 职业:Engineer, 字段数:3
行2, 姓名:Bob, 年龄:30, 职业:Designer, 字段数:3
行3, 姓名:Charlie, 年龄:28, 职业:ProductManager, 字段数:3

二、字段分隔符与记录分隔符(FS/RS)

默认分隔符可能无法满足需求(如 CSV 文件、自定义格式文本),需手动指定 FS(字段分隔符)或 RS(记录分隔符)。

2.1 指定字段分隔符(FS)

方式1:通过 -F 参数(命令行快捷方式)

格式:awk -F "分隔符" '操作' 文件名
示例1:处理 CSV 文件(逗号分隔)

# 准备CSV文件
cat > data.csv << EOF
ID,Name,Age,City
1,Alice,25,NewYork
2,Bob,30,London
3,Charlie,28,Paris
EOF

# 提取姓名和城市
awk -F "," '{print $2 "\t" $4}' data.csv

执行结果

Name    City
Alice   NewYork
Bob     London
Charlie Paris

示例2:多字符分隔符(如 ::

# 准备测试文件
cat > log.txt << EOF
2025-12-01::Alice::Login
2025-12-01::Bob::Logout
EOF

# 提取时间和操作
awk -F "::" '{print $1 " -> " $3}' log.txt

执行结果

2025-12-01 -> Login
2025-12-01 -> Logout
方式2:在脚本中设置 FS 变量
awk 'BEGIN{FS=","} {print $2 "\t" $4}' data.csv

执行结果:与示例1完全一致。

2.2 指定记录分隔符(RS)

默认一条记录是一行(RS="\n"),可自定义为其他字符(如分号、空格)。
示例:处理以分号分隔的记录

# 准备测试文件
cat > record.txt << EOF
Alice:25:Engineer;Bob:30:Designer;Charlie:28:ProductManager
EOF

# 以分号分隔记录,冒号分隔字段
awk 'BEGIN{RS=";"; FS=":"} {print "姓名:" $1 ", 年龄:" $2}' record.txt

执行结果

姓名:Alice, 年龄:25
姓名:Bob, 年龄:30
姓名:Charlie, 年龄:28

三、AWK 三大核心块(BEGIN/主块/END)

AWK 脚本由「可选块」组成,执行顺序固定:

  1. BEGIN{}读取文本前执行(仅1次),用于初始化变量、设置分隔符、打印表头;
  2. 「主块」(无关键字):逐行读取文本时执行(每行1次),用于处理数据(条件判断、字段提取等);
  3. END{}读取所有文本后执行(仅1次),用于汇总结果(统计、计算总数等)。

格式:

BEGIN {
  # 初始化操作(如设置FS、打印表头)
}
{
  # 逐行处理逻辑(主逻辑)
}
END {
  # 汇总操作(如统计总数、计算平均值)
}

实战案例:统计学生成绩

# 准备成绩文件
cat > scores.txt << EOF
Alice 85 92 78
Bob 76 88 90
Charlie 95 89 92
David 68 72 65
EOF

# 执行统计脚本
awk '
BEGIN {
  FS=" ";  # 字段分隔符(默认空格,可省略)
  OFS="\t";  # 输出字段分隔符(制表符)
  print "姓名", "语文", "数学", "英语", "总分";  # 表头
  print "----------------------------------------";
  total_sum = 0;  # 初始化全班总分
  count = 0;      # 初始化学生人数
}
{
  sum = $2 + $3 + $4;  # 计算每人总分
  print $1, $2, $3, $4, sum;
  total_sum += sum;    # 累加全班总分
  count++;             # 累加学生人数
}
END {
  print "----------------------------------------";
  avg = total_sum / (count * 3);  # 计算平均分(3科)
  printf "全班平均分: %.2f\n", avg;  # 格式化输出(保留2位小数)
  print "学生总数: " count;
}
' scores.txt

执行结果

姓名	语文	数学	英语	总分
----------------------------------------
Alice	85	92	78	255
Bob	76	88	90	254
Charlie	95	89	92	276
David	68	72	65	205
----------------------------------------
全班平均分: 81.67
学生总数: 4

四、条件判断(if/else if/else)

AWK 支持标准的条件判断语法,可根据字段值、行号等筛选数据。

语法格式

{
  if (条件1) {
    操作1;
  } else if (条件2) {
    操作2;
  } else {
    操作3;
  }
}

条件表达式支持的运算符

类型 运算符 示例
比较运算符 ==(等于)、!=(不等于)、>(大于)、<(小于)、>=<= $2 >= 90(第2字段≥90)
逻辑运算符 &&(与)、`
正则匹配 ~(匹配)、!~(不匹配) $1 ~ /^A/(姓名以A开头)

实战案例

案例1:筛选特定行(行号条件)
awk 'NR >= 2 && NR <= 3 {print $1 " 的成绩: " $2 " " $3 " " $4}' scores.txt

执行结果

Bob 的成绩: 76 88 90
Charlie 的成绩: 95 89 92
案例2:筛选符合条件的字段(数值条件)
awk '
{
  sum = $2 + $3 + $4;
  if ($3 >= 90 || sum >= 260) {
    print $1 ": 数学=" $3 ", 总分=" sum;
  }
}
' scores.txt

执行结果

Alice: 数学=92, 总分=255
Charlie: 数学=89, 总分=276
案例3:正则匹配筛选(文本条件)
awk '$1 ~ /^C/ || $3 == "Engineer" {print $0}' test.txt

执行结果

Alice 25 Engineer
Charlie 28 ProductManager

五、循环结构(for/while/do-while)

AWK 支持循环结构,可用于批量处理字段、重复执行操作等。

5.1 for 循环(最常用)

格式1:普通循环(类似 C 语言)
awk 'BEGIN{for (i=1; i<=5; i++) print i}'

执行结果

1
2
3
4
5
格式2:遍历字段(for-in 循环)
awk '{
  print "第" NR "行字段:";
  for (i=1; i<=NF; i++) {  # i 从1到 NF(字段总数)
    print "  字段" i ": " $i;
  }
}' test.txt

执行结果

第1行字段:
  字段1: Alice
  字段2: 25
  字段3: Engineer
第2行字段:
  字段1: Bob
  字段2: 30
  字段3: Designer
第3行字段:
  字段1: Charlie
  字段2: 28
  字段3: ProductManager

5.2 while 循环

awk 'BEGIN{
  i=1; sum=0;
  while (i<=10) {
    sum += i;
    i++;
  }
  print "1-10累加和:" sum;
}'

执行结果

1-10累加和:55

5.3 do-while 循环(先执行后判断)

awk 'BEGIN{
  i=11; sum=0;
  do {
    sum += i;
    i++;
  } while (i<=10);  # 条件不满足,但仍执行1次
  print sum;  # 输出 11
}'

执行结果

11

六、数组(关联数组)

AWK 只有「关联数组」(类似 Python 字典、Java Map),索引可以是字符串或数字,无需预先声明长度,动态扩容。

6.1 数组的基本操作(增删改查)

awk 'BEGIN{
  # 字符串索引
  user["name"] = "Alice";
  user["age"] = 25;
  # 数字索引
  score[1] = 85;
  score[2] = 92;
  
  # 查:打印数组值
  print user["name"], user["age"];  # Alice 25
  print score[1], score[2];        # 85 92
  
  # 改:修改值
  user["age"] = 26;
  print user["age"];  # 26
  
  # 删:删除元素(用 delete 关键字)
  delete score[2];
  print score[2];  # 空值(数组中无该索引)
}'

执行结果

Alice 25
85 92
26

遍历数组(for-in 循环)
awk 'BEGIN{
  fruit["a"] = "apple";
  fruit["b"] = "banana";
  fruit["c"] = "cherry";
  
  for (key in fruit) {
    print key ": " fruit[key];
  }
}'

执行结果(顺序可能不同):

a: apple
b: banana
c: cherry

6.2 实战案例:统计文本中单词出现次数

# 准备单词文件
cat > words.txt << EOF
Hello world Hello AWK world Python AWK
EOF

# 执行统计脚本
awk '
BEGIN{
  FS=" ";  # 按空格分割单词
  IGNORECASE=1;  # 忽略大小写(GNU AWK 扩展)
}
{
  for (i=1; i<=NF; i++) {
    word = $i;
    if (word != "") {  # 排除空字符串(避免多余空格)
      count[word]++;  # 统计次数
    }
  }
}
END{
  print "单词出现次数:";
  for (w in count) {
    print w ": " count[w];
  }
}' words.txt

执行结果

单词出现次数:
Hello: 2
world: 2
AWK: 2
Python: 1

七、正则表达式与模式匹配

AWK 内置强大的正则表达式支持,可用于字段匹配、行筛选、文本替换等。

7.1 正则匹配运算符

运算符 含义 示例
~ 字段匹配正则 $1 ~ /^A/(第1字段以A开头)
!~ 字段不匹配正则 $3 !~ /er$/(第3字段不以er结尾)

7.2 常用正则表达式元字符

元字符 含义 示例
^ 行首 /^Alice/(行以Alice开头)
$ 行尾 /Engineer$/(行以Engineer结尾)
. 任意单个字符 /A..e/(匹配A开头、e结尾的4个字符,如Alice)
* 前一个字符重复0次或多次 /a*/(匹配任意个a,包括空)
+ 前一个字符重复1次或多次 /a+/(匹配至少1个a)
? 前一个字符重复0次或1次 /a?/(匹配0或1个a)
[] 字符集合 /[0-9]/(匹配数字)、/[A-Za-z]/(匹配字母)
[^] 否定字符集合 /[^0-9]/(匹配非数字)
` ` 逻辑或
() 分组 `/A(li

7.3 实战案例

案例1:筛选包含数字的行
awk '/[0-9]/ {print $0}' test.txt

执行结果

Alice 25 Engineer
Bob 30 Designer
Charlie 28 ProductManager
案例2:提取日志中的IP地址(假设IP格式为xxx.xxx.xxx.xxx)
# 准备日志文件
cat > access.log << EOF
192.168.1.1 - [01/Dec/2025:10:00:00 +0800] "GET / HTTP/1.1" 200
10.0.0.1 - [01/Dec/2025:10:01:00 +0800] "POST /login HTTP/1.1" 302
EOF

# 提取IP
awk '{if ($1 ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/) print "IP: " $1}' access.log

执行结果

IP: 192.168.1.1
IP: 10.0.0.1
案例3:文本替换(gsub/sub 函数)
awk '
{
  sub(/25/, "二十五");  # 替换第一次出现的25
  gsub(/30/, "三十");   # 替换所有出现的30
  print $0;
}
' test.txt

执行结果

Alice 二十五 Engineer
Bob 三十 Designer
Charlie 28 ProductManager

八、常用内置函数

AWK 提供大量内置函数,覆盖字符串处理、数值计算、时间处理等场景,以下是高频函数:

8.1 字符串函数

函数 功能 示例
length(s) 返回字符串s的长度(默认$0) length($1)(姓名长度)
substr(s, start, len) 截取字符串s(从start位置开始,取len个字符) substr($1, 1, 3)(取姓名前3个字符)
index(s, t) 返回t在s中首次出现的位置(未找到返回0) index($3, "er")(职业中er的位置)
tolower(s) 转换为小写 tolower($1)(姓名小写)
toupper(s) 转换为大写 toupper($3)(职业大写)
split(s, arr, sep) 将s按sep分割,存入数组arr split($0, arr, " ")(分割整行到数组)

示例:字符串函数实战

awk '{
  name = $1;
  printf "姓名:%s, 长度:%d, 前2字符:%s, 大写:%s\n",
    name, length(name), substr(name,1,2), toupper(name);
}' test.txt

执行结果

姓名:Alice, 长度:5, 前2字符:Al, 大写:ALICE
姓名:Bob, 长度:3, 前2字符:Bo, 大写:BOB
姓名:Charlie, 长度:7, 前2字符:Ch, 大写:CHARLIE

8.2 数值函数

函数 功能 示例
int(n) 取整(丢弃小数部分) int(85.9) → 85
sqrt(n) 平方根 sqrt(100) → 10
rand() 生成0-1之间的随机数 rand() → 0.345(每次执行不同)
srand(n) 设置随机数种子(n为时间戳时每次不同) srand()(默认用时间戳)
max(a,b) 取最大值(GNU AWK) max($2,$3)(语文和数学的最高分)
min(a,b) 取最小值(GNU AWK) min($2,$3)(语文和数学的最低分)

示例:生成3个1-100的随机数

awk 'BEGIN{
  srand();  # 初始化随机数种子
  for (i=1; i<=3; i++) {
    print int(rand()*100) + 1;  # rand()*100 → 0-99.99,+1后1-100
  }
}'

执行结果(每次不同,示例):

45
89
12

8.3 时间函数(GNU AWK 扩展)

函数 功能 示例
mktime("YYYY MM DD HH MM SS") 将时间字符串转换为时间戳(秒数) mktime("2025 12 01 10 00 00")
strftime(format, timestamp) 将时间戳转换为指定格式的字符串 strftime("%Y-%m-%d %H:%M:%S")

示例:打印当前时间和指定时间戳的日期

awk 'BEGIN{
  # 当前时间
  now = systime();  # 获取当前时间戳
  print "当前时间:" strftime("%Y-%m-%d %H:%M:%S", now);
  
  # 转换指定时间戳(2025-12-01 00:00:00)
  ts = mktime("2025 12 01 00 00 00");
  print "指定时间:" strftime("%Y年%m月%d日", ts);
}'

执行结果(时间随实际运行时刻变化):

当前时间:2025-12-04 16:20:35
指定时间:2025年12月01日

九、高级实战场景

场景1:分析Nginx访问日志(统计Top10 IP)

# 准备Nginx日志
cat > nginx.log << EOF
192.168.1.1 [01/Dec/2025:10:00:00] GET / 200
10.0.0.1 [01/Dec/2025:10:01:00] POST /login 302
192.168.1.1 [01/Dec/2025:10:02:00] GET /about 200
192.168.1.2 [01/Dec/2025:10:03:00] GET / 200
192.168.1.1 [01/Dec/2025:10:04:00] GET /contact 200
EOF

# 统计Top10 IP
awk '
{
  ip = $1;
  count[ip]++;  # 统计每个IP的访问次数
}
END{
  # 将数组转为“IP:次数”字符串,存入临时数组
  for (i in count) {
    arr[++n] = i ":" count[i];
  }
  # 排序(按次数降序,GNU AWK 的 asorti 支持自定义排序)
  asorti(arr, sorted, "@val_num_desc");
  # 打印前10
  print "Top10 访问IP:";
  for (i=1; i<=10 && i<=n; i++) {
    split(arr[i], tmp, ":");
    print i ". " tmp[1] " → " tmp[2] "次";
  }
}' nginx.log

执行结果

Top10 访问IP:
1. 192.168.1.1 → 3次
2. 10.0.0.1 → 1次
3. 192.168.1.2 → 1次

场景2:格式化JSON输出(将CSV转为JSON)

# 复用之前的data.csv文件
awk '
BEGIN{
  FS=",";
  print "[";  # JSON数组开始
  first=1;  # 标记是否为第一个元素(避免末尾多余逗号)
}
NR>1{  # 跳过表头(NR>1)
  if (!first) print ",";  # 非第一个元素前加逗号
  printf '  {"ID":%d, "Name":"%s", "Age":%d}', $1, $2, $3;
  first=0;
}
END{
  print "\n]";  # JSON数组结束
}' data.csv

执行结果

[
  {"ID":1, "Name":"Alice", "Age":25},
  {"ID":2, "Name":"Bob", "Age":30},
  {"ID":3, "Name":"Charlie", "Age":28}
]

场景3:计算日志中各状态码的占比

# 复用之前的nginx.log文件
awk '
{
  code = $5;  # 状态码在第5字段
  total++;    # 总请求数
  code_count[code]++;  # 统计各状态码次数
}
END{
  print "状态码统计(总请求数:" total "):";
  for (c in code_count) {
    ratio = (code_count[c] / total) * 100;
    printf "  %s: %d次 (%.2f%%)\n", c, code_count[c], ratio;
  }
}' nginx.log

执行结果

状态码统计(总请求数:5):
  200: 4次 (80.00%)
  302: 1次 (20.00%)

十、AWK 高频场景速查表(按使用频率排序)

场景 核心命令/脚本 说明
打印指定字段 awk '{print $1, $3}' file 打印第1、3字段(默认空格分隔)
按行号筛选 awk 'NR==5 {print $0}' file 打印第5行;NR>=2 && NR<=10 打印2-10行
按字段值筛选 awk '$2>80 {print $0}' file 第2字段>80时打印整行
CSV文件处理 awk -F "," '{print $2}' data.csv 以逗号分隔,提取第2字段
统计行数 awk 'END{print NR}' file 等价于 wc -l file,更高效
统计字段数 awk '{print NF}' file 打印每行的字段总数
文本替换 awk '{gsub(/old/, "new"); print $0}' file 替换所有old为new
单词计数 awk '{for(i=1;i<=NF;i++) count[$i]++} END{for(w in count) print w,count[w]}' file 统计每个单词出现次数
计算总和/平均值 awk '{sum+=$2} END{print "总和:" sum, "平均值:" sum/NR}' file 计算第2字段的总和与平均值
筛选含指定字符串的行 awk '/error/ {print $0}' log.txt 打印包含error的行(正则匹配)
提取IP地址 awk '$1 ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/ {print $1}' log.txt 从日志中提取IP格式的字段
多文件处理 awk '{print FILENAME, NR, $0}' file1 file2 打印文件名、行号、内容
格式化输出 awk '{printf "姓名:%-10s 年龄:%d\n", $1, $2}' file 左对齐姓名(占10字符),年龄数字格式
排除空行 awk 'NF>0 {print $0}' file 仅打印非空行(空行NF=0)
按分隔符拼接字段 `awk 'BEGIN{OFS=" "} {print $1, $2, $3}’ file`

十一、常见问题与注意事项

  1. 字段分隔符问题:如果文本字段分隔符是多个空格/制表符,AWK 会自动合并为一个分隔符(默认行为),无需额外处理。
  2. 空行处理:空行的 NF=0,可通过 NF>0 筛选非空行:awk 'NF>0 {print $0}' file
  3. 变量作用域:AWK 中变量默认是全局的,若需局部变量,需在函数内用 var 声明(GNU AWK 扩展)。
  4. 正则匹配转义:正则中的特殊字符(如 .*)需转义(\.\*),或用字符集合 [] 包裹。
  5. Windows 兼容性:Windows 命令行中,单引号 ' 需替换为双引号 ",且内部双引号需转义(\")。
  6. 浮点数精度:AWK 中数值默认是浮点数,比较时需注意精度问题(如 1.0 == 1 为真,但 0.1+0.2 == 0.3 可能为假)。

十二、总结

AWK 的核心能力是「逐行处理 + 字段分割 + 灵活的逻辑操作」,掌握以下关键点即可应对90%的文本处理场景:

  1. 内置变量($0$nNRNFFS);
  2. 三大块(BEGIN/主块/END);
  3. 条件判断与循环;
  4. 正则匹配与字符串函数;
  5. 关联数组(统计场景必备)。
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐